@oneuptime/common 12.0.4 → 12.0.6
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/DatabaseModels/AIRun.ts +71 -1
- package/Models/DatabaseModels/AlertFeed.ts +30 -0
- package/Models/DatabaseModels/Dashboard.ts +28 -0
- package/Models/DatabaseModels/DataSource.ts +744 -0
- package/Models/DatabaseModels/Incident.ts +2 -0
- package/Models/DatabaseModels/IncidentEpisode.ts +1 -0
- package/Models/DatabaseModels/IncidentEpisodePublicNote.ts +1 -0
- package/Models/DatabaseModels/IncidentEpisodeStateTimeline.ts +1 -0
- package/Models/DatabaseModels/IncidentFeed.ts +30 -0
- package/Models/DatabaseModels/IncidentPublicNote.ts +1 -0
- package/Models/DatabaseModels/IncidentStateTimeline.ts +1 -0
- package/Models/DatabaseModels/Index.ts +3 -0
- package/Models/DatabaseModels/LogSavedView.ts +33 -0
- package/Models/DatabaseModels/Project.ts +231 -9
- package/Models/DatabaseModels/ProjectCallSMSConfig.ts +10 -2
- package/Models/DatabaseModels/ScheduledMaintenance.ts +1 -0
- package/Models/DatabaseModels/ScheduledMaintenancePublicNote.ts +1 -0
- package/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.ts +1 -0
- package/Models/DatabaseModels/ScheduledMaintenanceTemplateOwnerUser.ts +4 -4
- package/Models/DatabaseModels/StatusPage.ts +28 -0
- package/Models/DatabaseModels/StatusPageAnnouncement.ts +1 -0
- package/Models/DatabaseModels/User.ts +2 -0
- package/Models/DatabaseModels/UserOnCallLog.ts +2 -0
- package/Models/DatabaseModels/UserOnCallLogTimeline.ts +2 -0
- package/Models/DatabaseModels/UserSession.ts +2 -0
- package/Models/DatabaseModels/UserTotpAuth.ts +6 -4
- package/Models/DatabaseModels/UserWebAuthn.ts +5 -3
- package/Server/API/AIAgentDataAPI.ts +44 -55
- package/Server/API/AIInsightAPI.ts +47 -4
- package/Server/API/AIInvestigationAPI.ts +200 -17
- package/Server/API/DashboardAPI.ts +186 -502
- package/Server/API/DataSourceAPI.ts +268 -0
- package/Server/API/StatusPageAPI.ts +49 -20
- package/Server/API/UserAPI.ts +119 -0
- package/Server/EnvironmentConfig.ts +16 -0
- package/Server/Infrastructure/ClickhouseConfig.ts +74 -1
- package/Server/Infrastructure/ClickhouseDatabase.ts +1 -1
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786096660558-AddTimeRangeToLogSavedView.ts +27 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786101798351-MigrationName.ts +89 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786105470826-MigrationName.ts +27 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786303472848-AddDataSourceTable.ts +39 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786400000000-AddMasterPasswordSalt.ts +40 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786500000000-AddInvestigationCodeFixRecommendation.ts +26 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786600000000-AddInvestigationAnalysisTldr.ts +24 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786700000000-DropProjectCallSMSConfigCredentialUniques.ts +76 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +16 -0
- package/Server/Infrastructure/Queue.ts +26 -0
- package/Server/Infrastructure/QueueWorker.ts +17 -0
- package/Server/Middleware/TelemetryIngest.ts +8 -3
- package/Server/Services/AIInsightService.ts +132 -12
- package/Server/Services/AIRunService.ts +112 -10
- package/Server/Services/AIService.ts +109 -22
- package/Server/Services/AlertFeedService.ts +5 -0
- package/Server/Services/AlertService.ts +38 -32
- package/Server/Services/AnalyticsDatabaseService.ts +37 -4
- package/Server/Services/DataSourceService.ts +200 -0
- package/Server/Services/DatabaseService.ts +43 -2
- package/Server/Services/HostService.ts +92 -0
- package/Server/Services/IncidentFeedService.ts +5 -0
- package/Server/Services/IncidentService.ts +45 -0
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/LlmLogService.ts +46 -2
- package/Server/Services/MonitorService.ts +49 -0
- package/Server/Services/MonitorStatusService.ts +166 -0
- package/Server/Services/OpenTelemetryIngestService.ts +255 -184
- package/Server/Services/RumSessionPinService.ts +7 -21
- package/Server/Services/StatusPageSubscriberService.ts +13 -0
- package/Server/Services/UserService.ts +280 -9
- package/Server/Static/Vendor/README.md +62 -0
- package/Server/Static/Vendor/fonts/InterVariable.woff2 +0 -0
- package/Server/Static/Vendor/highlight/highlight.min.js +1244 -0
- package/Server/Static/Vendor/highlight/languages/apache.min.js +15 -0
- package/Server/Static/Vendor/highlight/languages/bash.min.js +21 -0
- package/Server/Static/Vendor/highlight/languages/c.min.js +41 -0
- package/Server/Static/Vendor/highlight/languages/cpp.min.js +47 -0
- package/Server/Static/Vendor/highlight/languages/csharp.min.js +49 -0
- package/Server/Static/Vendor/highlight/languages/css.min.js +31 -0
- package/Server/Static/Vendor/highlight/languages/diff.min.js +9 -0
- package/Server/Static/Vendor/highlight/languages/dns.min.js +11 -0
- package/Server/Static/Vendor/highlight/languages/dockerfile.min.js +8 -0
- package/Server/Static/Vendor/highlight/languages/go.min.js +20 -0
- package/Server/Static/Vendor/highlight/languages/graphql.min.js +12 -0
- package/Server/Static/Vendor/highlight/languages/http.min.js +14 -0
- package/Server/Static/Vendor/highlight/languages/ini.min.js +15 -0
- package/Server/Static/Vendor/highlight/languages/java.min.js +38 -0
- package/Server/Static/Vendor/highlight/languages/javascript.min.js +81 -0
- package/Server/Static/Vendor/highlight/languages/json.min.js +8 -0
- package/Server/Static/Vendor/highlight/languages/kotlin.min.js +46 -0
- package/Server/Static/Vendor/highlight/languages/lua.min.js +15 -0
- package/Server/Static/Vendor/highlight/languages/makefile.min.js +14 -0
- package/Server/Static/Vendor/highlight/languages/nginx.min.js +21 -0
- package/Server/Static/Vendor/highlight/languages/perl.min.js +41 -0
- package/Server/Static/Vendor/highlight/languages/php.min.js +59 -0
- package/Server/Static/Vendor/highlight/languages/powershell.min.js +39 -0
- package/Server/Static/Vendor/highlight/languages/python.min.js +42 -0
- package/Server/Static/Vendor/highlight/languages/r.min.js +26 -0
- package/Server/Static/Vendor/highlight/languages/ruby.min.js +54 -0
- package/Server/Static/Vendor/highlight/languages/rust.min.js +27 -0
- package/Server/Static/Vendor/highlight/languages/scala.min.js +28 -0
- package/Server/Static/Vendor/highlight/languages/sql.min.js +21 -0
- package/Server/Static/Vendor/highlight/languages/swift.min.js +68 -0
- package/Server/Static/Vendor/highlight/languages/typescript.min.js +99 -0
- package/Server/Static/Vendor/highlight/languages/xml.min.js +29 -0
- package/Server/Static/Vendor/highlight/languages/yaml.min.js +28 -0
- package/Server/Static/Vendor/highlight/styles/github-dark.min.css +10 -0
- package/Server/Static/Vendor/highlight/styles/vs2015.min.css +1 -0
- package/Server/Static/Vendor/tailwind/tailwind-3.4.5.js +21028 -0
- package/Server/Types/Database/Permissions/BasePermission.ts +7 -0
- package/Server/Types/Database/Permissions/DeletePermission.ts +2 -0
- package/Server/Types/Database/Permissions/ReadPermission.ts +3 -0
- package/Server/Types/Database/Permissions/TenantPermission.ts +101 -16
- package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +6 -0
- package/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.ts +4 -0
- package/Server/Utils/AI/CodeFix/CodeFixReadiness.ts +8 -5
- package/Server/Utils/AI/CodeFix/FixRunBudget.ts +90 -16
- package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +2 -0
- package/Server/Utils/AI/Remediation/RemediationPlanRunner.ts +2 -0
- package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +311 -31
- package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +26 -24
- package/Server/Utils/AI/SRE/ConfidenceSignal.ts +182 -71
- package/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.ts +252 -43
- package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +26 -24
- package/Server/Utils/AI/SRE/Insights/Detectors/ExceptionIdentity.ts +125 -0
- package/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.ts +25 -15
- package/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.ts +256 -57
- package/Server/Utils/AI/SRE/Insights/InsightStore.ts +76 -18
- package/Server/Utils/AI/SRE/InstrumentationTaskTrigger.ts +47 -13
- package/Server/Utils/AI/SRE/InvestigationGrader.ts +9 -5
- package/Server/Utils/AI/SRE/InvestigationQueue.ts +141 -24
- package/Server/Utils/AI/SRE/InvestigationSubjectLock.ts +66 -0
- package/Server/Utils/AI/SRE/InvestigationTldr.ts +352 -0
- package/Server/Utils/AI/SRE/PostedRootCause.ts +94 -3
- package/Server/Utils/AI/SRE/README.md +1 -1
- package/Server/Utils/AI/SRE/SubjectCodeFixRun.ts +132 -10
- package/Server/Utils/AnalyticsDatabase/ClusterConfig.ts +24 -0
- package/Server/Utils/AnalyticsDatabase/InsertDedupContext.ts +7 -2
- package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +1793 -0
- package/Server/Utils/Dashboard/PublicDashboardViewConfig.ts +65 -0
- package/Server/Utils/DataSource/ConnectorRegistry.ts +53 -0
- package/Server/Utils/DataSource/Connectors/ClickHouseConnector.ts +338 -0
- package/Server/Utils/DataSource/Connectors/ElasticsearchConnector.ts +717 -0
- package/Server/Utils/DataSource/Connectors/LokiConnector.ts +606 -0
- package/Server/Utils/DataSource/Connectors/MySqlConnector.ts +307 -0
- package/Server/Utils/DataSource/Connectors/PostgresConnector.ts +260 -0
- package/Server/Utils/DataSource/Connectors/PrometheusConnector.ts +555 -0
- package/Server/Utils/DataSource/Connectors/RestApiConnector.ts +597 -0
- package/Server/Utils/DataSource/Connectors/SqlServerConnector.ts +318 -0
- package/Server/Utils/DataSource/EgressGuard.ts +446 -0
- package/Server/Utils/DataSource/HttpFetch.ts +221 -0
- package/Server/Utils/DataSource/SqlQueryGuard.ts +383 -0
- package/Server/Utils/DataSource/SqlResultShaper.ts +372 -0
- package/Server/Utils/DataSource/Types.ts +93 -0
- package/Server/Utils/Database/PostgresErrorTranslator.ts +172 -17
- package/Server/Utils/InProcessMemo.ts +141 -0
- package/Server/Utils/InstanceHealth/TelemetryIngestion.ts +628 -0
- package/Server/Utils/Monitor/Criteria/APIRequestCriteria.ts +29 -0
- package/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.ts +56 -0
- package/Server/Utils/Monitor/MonitorMetricUtil.ts +104 -0
- package/Server/Utils/Monitor/MonitorResource.ts +14 -0
- package/Server/Utils/Monitor/MonitorTemplateUtil.ts +1 -0
- package/Server/Utils/PasswordHash.ts +28 -4
- package/Server/Utils/Response.ts +12 -1
- package/Server/Utils/SSRFProtection.ts +237 -0
- package/Server/Utils/StartServer.ts +8 -0
- package/Server/Utils/StatusPageSubscriberWebhook.ts +30 -0
- package/Server/Utils/Telemetry/CaptureSpan.ts +30 -18
- package/Server/Utils/Telemetry/IngestionTimestamp.ts +75 -0
- package/Server/Utils/Telemetry/ResourceHeartbeat.ts +99 -7
- package/Server/Utils/Telemetry/Telemetry.ts +112 -1
- package/Server/Utils/Telemetry.ts +104 -3
- package/Server/Utils/VendorAssets.ts +165 -0
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +28 -5
- package/Server/Utils/Workspace/Slack/Slack.ts +5 -0
- package/Server/Views/Partials/Head.ejs +21 -7
- package/Tests/App/AdminDashboard/AdminProjectsIdFilter.test.tsx +207 -0
- package/Tests/App/AdminDashboard/AdminUsersIdFilter.test.tsx +216 -0
- package/Tests/App/Dashboard/AIInsightViewActions.test.tsx +422 -0
- package/Tests/App/Dashboard/AIInvestigationHeaderStatus.test.tsx +251 -0
- package/Tests/App/Dashboard/AddWidgetModal.test.tsx +10 -3
- package/Tests/App/Dashboard/AlertsSideMenu.test.tsx +387 -0
- package/Tests/App/Dashboard/ChatActivityFeed.test.tsx +199 -0
- package/Tests/App/Dashboard/DataSourceChartSyntheticConfig.test.tsx +736 -0
- package/Tests/App/Dashboard/DataSourceQueryEditor.test.tsx +868 -0
- package/Tests/App/Dashboard/DataSourceWidgetCatalog.test.ts +440 -0
- package/Tests/App/Dashboard/DataSourceWidgetFetching.test.tsx +2109 -0
- package/Tests/App/Dashboard/DataSourceWidgetViews.test.tsx +726 -0
- package/Tests/App/Dashboard/EventStatusPanel.test.tsx +747 -0
- package/Tests/App/Dashboard/IncidentsSideMenu.test.tsx +447 -0
- package/Tests/App/Dashboard/InvestigationFeedRefresh.test.tsx +480 -0
- package/Tests/App/Dashboard/InvestigationPanel.test.tsx +1695 -0
- package/Tests/App/Dashboard/InvestigationPanelStatus.test.tsx +500 -0
- package/Tests/App/Dashboard/PortMonitorCriteriaFilter.test.ts +93 -0
- package/Tests/App/Dashboard/ProductMenuBreadcrumbs.test.tsx +391 -0
- package/Tests/App/Dashboard/ScheduledMaintenanceSideMenu.test.tsx +326 -0
- package/Tests/App/Dashboard/SideMenuHarness.tsx +187 -0
- package/Tests/App/Dashboard/WidgetCatalog.test.ts +6 -1
- package/Tests/App/StatusPage/PublicStatusPageAPIErrorHandling.test.ts +117 -0
- package/Tests/App/StatusPage/StatusPageModelAPIPolymorphism.test.ts +223 -0
- package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +1858 -0
- package/Tests/Models/DatabaseModels/DataSourceModelSecrets.test.ts +70 -0
- package/Tests/Models/DatabaseModels/PermissionCatalogueCoverage.test.ts +243 -0
- package/Tests/Server/API/AIAgentDataFixFromIncidentContext.test.ts +266 -0
- package/Tests/Server/API/AIInvestigationAPI.test.ts +781 -0
- package/Tests/Server/API/AIInvestigationCreateFixTask.test.ts +80 -0
- package/Tests/Server/API/DashboardMasterPasswordAPI.test.ts +172 -1
- package/Tests/Server/API/DashboardPublicDataSourceStrip.test.ts +686 -0
- package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +585 -823
- package/Tests/Server/API/DataSourceAPIRoutes.test.ts +486 -0
- package/Tests/Server/API/StatusPageMasterPasswordAPI.test.ts +335 -0
- package/Tests/Server/API/StatusPageSubscriberOwnership.test.ts +291 -0
- package/Tests/Server/API/UserAuthenticationAPI.test.ts +641 -0
- package/Tests/Server/Infrastructure/ClickhouseKeepAliveOptions.test.ts +441 -0
- package/Tests/Server/Middleware/TelemetryIngestTokenLog.test.ts +140 -0
- package/Tests/Server/Services/AIInsightService.test.ts +266 -11
- package/Tests/Server/Services/AIRunAnalysisTldrPersistence.test.ts +160 -0
- package/Tests/Server/Services/AIRunCodeFixClaim.test.ts +59 -5
- package/Tests/Server/Services/AIRunHumanVerdict.test.ts +127 -58
- package/Tests/Server/Services/AIServiceDailyBudget.test.ts +400 -20
- package/Tests/Server/Services/AddMasterPasswordSaltMigration.test.ts +283 -0
- package/Tests/Server/Services/AlertMetricResourceAttributes.test.ts +352 -0
- package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +25 -1
- package/Tests/Server/Services/DataSourceServiceSettings.test.ts +244 -0
- package/Tests/Server/Services/DatabaseServicePerUserPasswordSalt.test.ts +45 -35
- package/Tests/Server/Services/DatabaseServiceUpdateColumnsWithoutHooks.test.ts +54 -0
- package/Tests/Server/Services/FeedAIRunAssociation.test.ts +125 -0
- package/Tests/Server/Services/FixFromIncidentTaskTrigger.test.ts +418 -57
- package/Tests/Server/Services/HostServiceFindOrCreateMemo.test.ts +397 -0
- package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +41 -2
- package/Tests/Server/Services/IncidentMetricResourceAttributes.test.ts +408 -0
- package/Tests/Server/Services/InstrumentationTaskTrigger.test.ts +173 -14
- package/Tests/Server/Services/MasterPasswordScrypt.test.ts +567 -0
- package/Tests/Server/Services/MonitorStatusServiceDeleteReferenceCleanup.test.ts +351 -0
- package/Tests/Server/Services/MonitorStatusServiceValidation.test.ts +171 -0
- package/Tests/Server/Services/OpenTelemetryResourceRetentionMemo.test.ts +398 -0
- package/Tests/Server/Services/OpenTelemetryServiceResolutionCache.test.ts +19 -0
- package/Tests/Server/Services/ProjectCallSMSConfigCredentialUniqueness.test.ts +305 -0
- package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +36 -1
- package/Tests/Server/Services/RumSessionPinUniqueViolationRecovery.test.ts +163 -0
- package/Tests/Server/Services/SeparateIncidentAlertAiSettingsMigration.test.ts +194 -0
- package/Tests/Server/Services/UserAuthenticationService.test.ts +973 -0
- package/Tests/Server/Types/Database/ObjectIdColumnQuery.test.ts +262 -0
- package/Tests/Server/Types/Database/Permissions/TenantPermission.test.ts +358 -0
- package/Tests/Server/Utils/AI/AIAlertGating.test.ts +37 -4
- package/Tests/Server/Utils/AI/AIConfidenceSignal.test.ts +330 -29
- package/Tests/Server/Utils/AI/AIIncidentGating.test.ts +25 -0
- package/Tests/Server/Utils/AI/AIInvestigationQueue.test.ts +293 -5
- package/Tests/Server/Utils/AI/CodeFixAgentCompletion.test.ts +59 -1
- package/Tests/Server/Utils/AI/FixFromIncidentAutoTrigger.test.ts +342 -19
- package/Tests/Server/Utils/AI/FixRunBudget.test.ts +214 -30
- package/Tests/Server/Utils/AI/Insights/ExceptionSpikeDetector.test.ts +6 -1
- package/Tests/Server/Utils/AI/Insights/InsightStore.test.ts +121 -4
- package/Tests/Server/Utils/AI/Insights/InvestigationQueueInsightSubject.test.ts +2 -0
- package/Tests/Server/Utils/AI/Insights/NewExceptionDetector.test.ts +361 -10
- package/Tests/Server/Utils/AI/InvestigationGrader.test.ts +65 -17
- package/Tests/Server/Utils/AI/InvestigationQueueRemediationExecution.test.ts +3 -0
- package/Tests/Server/Utils/AI/InvestigationSettlement.test.ts +605 -20
- package/Tests/Server/Utils/AI/InvestigationTldr.test.ts +623 -0
- package/Tests/Server/Utils/AI/InvestigationTldrWiring.test.ts +98 -0
- package/Tests/Server/Utils/AI/PostedRootCause.test.ts +254 -0
- package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +4 -0
- package/Tests/Server/Utils/AI/RemediationPlanRunner.test.ts +2 -0
- package/Tests/Server/Utils/AI/SubjectCodeFixRunDedupe.test.ts +44 -0
- package/Tests/Server/Utils/AnalyticsDatabase/ClusterAwareSchema.test.ts +30 -0
- package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +857 -0
- package/Tests/Server/Utils/Dashboard/PublicDashboardViewConfig.test.ts +480 -0
- package/Tests/Server/Utils/DataSource/ClickHouseConnector.test.ts +759 -0
- package/Tests/Server/Utils/DataSource/ConnectorRegistry.test.ts +37 -0
- package/Tests/Server/Utils/DataSource/EgressGuard.test.ts +645 -0
- package/Tests/Server/Utils/DataSource/ElasticsearchConnector.test.ts +1206 -0
- package/Tests/Server/Utils/DataSource/HttpFetch.test.ts +616 -0
- package/Tests/Server/Utils/DataSource/LokiConnector.test.ts +970 -0
- package/Tests/Server/Utils/DataSource/MySqlConnector.test.ts +935 -0
- package/Tests/Server/Utils/DataSource/PostgresConnector.test.ts +854 -0
- package/Tests/Server/Utils/DataSource/PrometheusConnector.test.ts +881 -0
- package/Tests/Server/Utils/DataSource/RestApiConnector.test.ts +1020 -0
- package/Tests/Server/Utils/DataSource/SqlQueryGuard.test.ts +425 -0
- package/Tests/Server/Utils/DataSource/SqlResultShaper.test.ts +555 -0
- package/Tests/Server/Utils/DataSource/SqlServerConnector.test.ts +934 -0
- package/Tests/Server/Utils/Database/PostgresErrorTranslator.test.ts +228 -7
- package/Tests/Server/Utils/InProcessMemo.test.ts +292 -0
- package/Tests/Server/Utils/InstanceHealth/TelemetryIngestion.test.ts +960 -0
- package/Tests/Server/Utils/MicrosoftTeamsWebhookUrlValidation.test.ts +103 -0
- package/Tests/Server/Utils/Monitor/Criteria/APIRequestCriteriaPortTimings.test.ts +212 -0
- package/Tests/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.test.ts +401 -0
- package/Tests/Server/Utils/Monitor/MonitorMetricResourceAttributes.test.ts +336 -0
- package/Tests/Server/Utils/Monitor/MonitorMetricUtilPortTimings.test.ts +243 -0
- package/Tests/Server/Utils/OfflineAssetHygiene.test.ts +521 -0
- package/Tests/Server/Utils/PasswordHash.test.ts +30 -0
- package/Tests/Server/Utils/ResponseRenderAnalytics.test.ts +143 -0
- package/Tests/Server/Utils/SSRFProtection.test.ts +193 -0
- package/Tests/Server/Utils/StatusPageSubscriberWebhook.test.ts +101 -0
- package/Tests/Server/Utils/TailwindBuildCopy.test.ts +225 -0
- package/Tests/Server/Utils/Telemetry/ResourceHeartbeatL1Memo.test.ts +355 -0
- package/Tests/Server/Utils/Telemetry/TelemetryFanInWriter.test.ts +128 -0
- package/Tests/Server/Utils/Telemetry/TelemetryUtilAttributeKeys.test.ts +381 -0
- package/Tests/Server/Utils/Telemetry.test.ts +87 -0
- package/Tests/Server/Utils/VendorAssetIntegrity.test.ts +343 -0
- package/Tests/Server/Utils/VendorAssets.test.ts +671 -0
- package/Tests/Server/Views/OfflineRender.test.ts +297 -0
- package/Tests/Types/Dashboard/DashboardTemplates.test.ts +111 -0
- package/Tests/Types/Dashboard/DataSourceComponentTypes.test.ts +618 -0
- package/Tests/Types/DateClickhouseFormat.test.ts +192 -0
- package/Tests/Types/Monitor/CephMetricCatalog.test.ts +104 -0
- package/Tests/Types/Monitor/CriteriaFilter.test.ts +4 -0
- package/Tests/Types/Monitor/DockerMetricCatalog.test.ts +109 -0
- package/Tests/Types/Monitor/DockerSwarmMetricCatalog.test.ts +118 -0
- package/Tests/Types/Monitor/HostMetricCatalog.test.ts +104 -0
- package/Tests/Types/Monitor/IotMetricCatalog.test.ts +104 -0
- package/Tests/Types/Monitor/KubernetesMetricCatalog.test.ts +118 -0
- package/Tests/Types/Monitor/MonitorCriteriaInstance.test.ts +42 -0
- package/Tests/Types/Monitor/MonitorStep.test.ts +38 -0
- package/Tests/Types/Monitor/MonitorStepDomainMonitor.test.ts +75 -0
- package/Tests/Types/Monitor/PodmanMetricCatalog.test.ts +109 -0
- package/Tests/Types/Monitor/ProxmoxMetricCatalog.test.ts +107 -0
- package/Tests/Types/Monitor/SnmpMonitor/SnmpVendorTemplate.test.ts +151 -0
- package/Tests/Types/Password.test.ts +363 -0
- package/Tests/Types/Permission.test.ts +137 -0
- package/Tests/Types/Rum/SessionReplayMaskingMode.test.ts +101 -0
- package/Tests/UI/Components/AiInvestigationSettingsCard.test.tsx +5 -5
- package/Tests/UI/Components/CardModelDetailEdit.test.tsx +3 -5
- package/Tests/UI/Components/ConfirmModal.test.tsx +7 -0
- package/Tests/UI/Components/ErrorBoundary.test.tsx +211 -0
- package/Tests/UI/Components/FeedItemSafeMode.test.tsx +74 -0
- package/Tests/UI/Components/Modal.test.tsx +281 -1
- package/Tests/UI/Components/MoreMenu.test.tsx +756 -0
- package/Tests/UI/Components/ObjectIdFilterPipeline.test.tsx +396 -0
- package/Tests/UI/Components/StatusPage/ResourceGroupNavigator.test.tsx +1147 -0
- package/Tests/UI/Components/StatusPage/ResourceGroupSection.test.tsx +62 -0
- package/Tests/UI/Monitor/PortMonitorView.test.tsx +237 -0
- package/Tests/UI/Telemetry/TelemetrySnapshotWindowAlert.test.tsx +91 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +116 -1
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +2 -0
- package/Tests/UI/Utils/DownloadFile.test.ts +103 -0
- package/Tests/UI/Utils/ErrorSupportBundle.test.ts +582 -0
- package/Tests/UI/Utils/Project.test.ts +31 -0
- package/Tests/UI/Utils/StatusPageModelAPIInjection.test.ts +245 -0
- package/Tests/UI/Utils/UseDashboardGridDnd.test.tsx +820 -0
- package/Tests/Utils/Analytics.test.ts +23 -0
- package/Tests/Utils/Dashboard/Components/DashboardDataSourceComponents.test.ts +640 -0
- package/Tests/Utils/Dashboard/DashboardViewConfig.test.ts +329 -0
- package/Tests/Utils/Dashboard/GridLayout.test.ts +951 -0
- package/Tests/Utils/DataSource/DataSourceQueryText.test.ts +138 -0
- package/Tests/Utils/DataSource/DataSourceValueReducer.test.ts +526 -0
- package/Tests/Utils/Metrics/MetricResourceAttributeUtil.test.ts +691 -0
- package/Tests/Utils/Monitor/MonitorMetricType.test.ts +117 -0
- package/Tests/Utils/RecordingRuleExpression.test.ts +327 -0
- package/Tests/Utils/StatusPage/GroupHierarchyView.test.ts +1452 -0
- package/Tests/Utils/StatusPage/GroupNestingLayout.test.ts +186 -0
- package/Tests/Utils/StatusPage/GroupTree.test.ts +456 -0
- package/Tests/Utils/StatusPage/OverviewGroupHierarchyVisibility.test.ts +588 -0
- package/Tests/Utils/StatusPage/ResourceExplorer.test.ts +931 -0
- package/Tests/Utils/Telemetry/CaptureSpanExportGating.test.ts +288 -0
- package/Tests/Utils/Telemetry/IngestionTimestamp.test.ts +170 -0
- package/Tests/Utils/Telemetry/SavedViewTimeRange.test.ts +244 -0
- package/Tests/Utils/Telemetry/TelemetryQueryTimeRange.test.ts +825 -0
- package/Types/AI/AIInsightEvidence.ts +6 -0
- package/Types/AI/AIRunCodeFixRecommendation.ts +17 -0
- package/Types/AI/CodeFixTaskContext.ts +63 -3
- package/Types/AI/CodeFixTaskType.ts +3 -3
- package/Types/Analytics/RevenueEvent.ts +31 -0
- package/Types/Dashboard/DashboardComponentType.ts +31 -0
- package/Types/Dashboard/DashboardComponents/DashboardDataSourceChartComponent.ts +27 -0
- package/Types/Dashboard/DashboardComponents/DashboardDataSourceGaugeComponent.ts +32 -0
- package/Types/Dashboard/DashboardComponents/DashboardDataSourceTableComponent.ts +30 -0
- package/Types/Dashboard/DashboardComponents/DashboardDataSourceValueComponent.ts +55 -0
- package/Types/DataSource/DataSourceLimits.ts +62 -0
- package/Types/DataSource/DataSourceQueryConfig.ts +41 -0
- package/Types/DataSource/DataSourceTableResult.ts +38 -0
- package/Types/DataSource/DataSourceType.ts +213 -0
- package/Types/Date.ts +43 -3
- package/Types/HashedString.ts +4 -3
- package/Types/Monitor/CriteriaFilter.ts +4 -0
- package/Types/Monitor/DomainMonitor/DomainLookupMethod.ts +23 -0
- package/Types/Monitor/DomainMonitor/DomainMonitorResponse.ts +7 -0
- package/Types/Monitor/MonitorCriteriaInstance.ts +24 -7
- package/Types/Monitor/MonitorMetricType.ts +8 -0
- package/Types/Monitor/MonitorStep.ts +10 -1
- package/Types/Monitor/MonitorStepDomainMonitor.ts +21 -0
- package/Types/Monitor/PortMonitor/PortMonitorTimings.ts +10 -0
- package/Types/Password.ts +64 -0
- package/Types/Permission.ts +69 -175
- package/Types/Probe/ProbeMonitorResponse.ts +6 -0
- package/Types/UserAuthenticationStatus.ts +34 -0
- package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +2 -3
- package/UI/Components/Button/Button.tsx +1 -1
- package/UI/Components/ErrorBoundary.tsx +549 -71
- package/UI/Components/Feed/FeedItem.tsx +9 -2
- package/UI/Components/Icon/Icon.tsx +10 -9
- package/UI/Components/Modal/ConfirmModal.tsx +6 -1
- package/UI/Components/Modal/Modal.tsx +267 -35
- package/UI/Components/Modal/ModalFooter.tsx +6 -1
- package/UI/Components/ModelDetail/CardModelDetail.tsx +0 -12
- package/UI/Components/ModelTable/BaseModelTable.tsx +1 -1
- package/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.ts +12 -2
- package/UI/Components/MoreMenu/MoreMenu.tsx +301 -98
- package/UI/Components/MoreMenu/MoreMenuItem.tsx +13 -5
- package/UI/Components/StatusPage/ResourceGrid.tsx +314 -0
- package/UI/Components/StatusPage/ResourceGroupNavigator.tsx +808 -0
- package/UI/Components/StatusPage/ResourceList.tsx +570 -0
- package/UI/Styles/Theme.css +18 -0
- package/UI/Utils/API/RequestOptions.ts +7 -0
- package/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.ts +1 -0
- package/UI/Utils/DownloadFile.ts +16 -8
- package/UI/Utils/ErrorSupportBundle.ts +805 -0
- package/UI/Utils/ModelAPI/ModelAPI.ts +20 -8
- package/UI/Utils/Project.ts +4 -0
- package/UI/Utils/StatusPage.ts +8 -2
- package/UI/Utils/TestDataSource.ts +61 -0
- package/UI/Utils/UseDashboardGridDnd.ts +917 -0
- package/UI/esbuild-config.js +45 -0
- package/Utils/Analytics.ts +15 -0
- package/Utils/Dashboard/Components/DashboardDataSourceChartComponent.ts +88 -0
- package/Utils/Dashboard/Components/DashboardDataSourceGaugeComponent.ts +123 -0
- package/Utils/Dashboard/Components/DashboardDataSourceShared.ts +16 -0
- package/Utils/Dashboard/Components/DashboardDataSourceTableComponent.ts +72 -0
- package/Utils/Dashboard/Components/DashboardDataSourceValueComponent.ts +126 -0
- package/Utils/Dashboard/Components/Index.ts +28 -0
- package/Utils/Dashboard/DashboardViewConfig.ts +80 -36
- package/Utils/Dashboard/GridLayout.ts +523 -0
- package/Utils/DataSource/DataSourceQueryText.ts +94 -0
- package/Utils/DataSource/DataSourceValueReducer.ts +156 -0
- package/Utils/Metrics/MetricResourceAttributeUtil.ts +380 -0
- package/Utils/Metrics/RecordingRuleExpression.ts +19 -17
- package/Utils/Monitor/MonitorMetricType.ts +44 -2
- package/Utils/NetworkDiscovery/ScanTargetUtil.ts +8 -6
- package/Utils/StatusPage/GroupHierarchyView.ts +931 -0
- package/Utils/StatusPage/GroupNestingLayout.ts +69 -0
- package/Utils/StatusPage/GroupTree.ts +157 -66
- package/Utils/StatusPage/ResourceExplorer.ts +853 -0
- package/Utils/StatusPage/ResourceUptime.ts +37 -2
- package/Utils/Telemetry/SavedViewTimeRange.ts +114 -0
- package/Utils/Telemetry/TelemetryQueryTimeRange.ts +334 -0
- package/build/dist/Models/DatabaseModels/AIRun.js +73 -1
- package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertFeed.js +32 -0
- package/build/dist/Models/DatabaseModels/AlertFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Dashboard.js +29 -0
- package/build/dist/Models/DatabaseModels/Dashboard.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DataSource.js +774 -0
- package/build/dist/Models/DatabaseModels/DataSource.js.map +1 -0
- package/build/dist/Models/DatabaseModels/Incident.js +2 -0
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisode.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentFeed.js +32 -0
- package/build/dist/Models/DatabaseModels/IncidentFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentPublicNote.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentPublicNote.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/LogSavedView.js +33 -0
- package/build/dist/Models/DatabaseModels/LogSavedView.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +241 -11
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProjectCallSMSConfig.js +14 -4
- package/build/dist/Models/DatabaseModels/ProjectCallSMSConfig.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplateOwnerUser.js +4 -4
- package/build/dist/Models/DatabaseModels/StatusPage.js +29 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js +1 -0
- package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js.map +1 -1
- package/build/dist/Models/DatabaseModels/User.js +2 -0
- package/build/dist/Models/DatabaseModels/User.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserOnCallLog.js +2 -0
- package/build/dist/Models/DatabaseModels/UserOnCallLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +2 -0
- package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserSession.js +2 -0
- package/build/dist/Models/DatabaseModels/UserSession.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserTotpAuth.js +6 -4
- package/build/dist/Models/DatabaseModels/UserTotpAuth.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserWebAuthn.js +5 -3
- package/build/dist/Models/DatabaseModels/UserWebAuthn.js.map +1 -1
- package/build/dist/Server/API/AIAgentDataAPI.js +28 -46
- package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
- package/build/dist/Server/API/AIInsightAPI.js +34 -4
- package/build/dist/Server/API/AIInsightAPI.js.map +1 -1
- package/build/dist/Server/API/AIInvestigationAPI.js +134 -11
- package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
- package/build/dist/Server/API/DashboardAPI.js +127 -438
- package/build/dist/Server/API/DashboardAPI.js.map +1 -1
- package/build/dist/Server/API/DataSourceAPI.js +173 -0
- package/build/dist/Server/API/DataSourceAPI.js.map +1 -0
- package/build/dist/Server/API/StatusPageAPI.js +40 -12
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/API/UserAPI.js +94 -2
- package/build/dist/Server/API/UserAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +14 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/ClickhouseConfig.js +61 -1
- package/build/dist/Server/Infrastructure/ClickhouseConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/ClickhouseDatabase.js +1 -1
- package/build/dist/Server/Infrastructure/ClickhouseDatabase.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786096660558-AddTimeRangeToLogSavedView.js +22 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786096660558-AddTimeRangeToLogSavedView.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786101798351-MigrationName.js +36 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786101798351-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786105470826-MigrationName.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786105470826-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786303472848-AddDataSourceTable.js +20 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786303472848-AddDataSourceTable.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786400000000-AddMasterPasswordSalt.js +19 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786400000000-AddMasterPasswordSalt.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786500000000-AddInvestigationCodeFixRecommendation.js +19 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786500000000-AddInvestigationCodeFixRecommendation.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786600000000-AddInvestigationAnalysisTldr.js +19 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786600000000-AddInvestigationAnalysisTldr.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786700000000-DropProjectCallSMSConfigCredentialUniques.js +64 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786700000000-DropProjectCallSMSConfigCredentialUniques.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/Queue.js +27 -0
- package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
- package/build/dist/Server/Infrastructure/QueueWorker.js +13 -0
- package/build/dist/Server/Infrastructure/QueueWorker.js.map +1 -1
- package/build/dist/Server/Middleware/TelemetryIngest.js +8 -3
- package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
- package/build/dist/Server/Services/AIInsightService.js +110 -10
- package/build/dist/Server/Services/AIInsightService.js.map +1 -1
- package/build/dist/Server/Services/AIRunService.js +92 -12
- package/build/dist/Server/Services/AIRunService.js.map +1 -1
- package/build/dist/Server/Services/AIService.js +88 -21
- package/build/dist/Server/Services/AIService.js.map +1 -1
- package/build/dist/Server/Services/AlertFeedService.js +3 -0
- package/build/dist/Server/Services/AlertFeedService.js.map +1 -1
- package/build/dist/Server/Services/AlertService.js +28 -35
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +37 -5
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DataSourceService.js +159 -0
- package/build/dist/Server/Services/DataSourceService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseService.js +21 -2
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/HostService.js +74 -0
- package/build/dist/Server/Services/HostService.js.map +1 -1
- package/build/dist/Server/Services/IncidentFeedService.js +3 -0
- package/build/dist/Server/Services/IncidentFeedService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +35 -24
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +2 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/LlmLogService.js +22 -1
- package/build/dist/Server/Services/LlmLogService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +45 -0
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/MonitorStatusService.js +129 -0
- package/build/dist/Server/Services/MonitorStatusService.js.map +1 -1
- package/build/dist/Server/Services/OpenTelemetryIngestService.js +222 -159
- package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
- package/build/dist/Server/Services/RumSessionPinService.js +7 -15
- package/build/dist/Server/Services/RumSessionPinService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberService.js +10 -0
- package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +241 -10
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/BasePermission.js +6 -0
- package/build/dist/Server/Types/Database/Permissions/BasePermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/DeletePermission.js +1 -0
- package/build/dist/Server/Types/Database/Permissions/DeletePermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/ReadPermission.js +2 -0
- package/build/dist/Server/Types/Database/Permissions/ReadPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/TenantPermission.js +68 -12
- package/build/dist/Server/Types/Database/Permissions/TenantPermission.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +3 -0
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js +4 -0
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js.map +1 -1
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js +8 -5
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js.map +1 -1
- package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js +63 -24
- package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +2 -0
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js +2 -0
- package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +222 -34
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +22 -19
- package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/ConfidenceSignal.js +128 -49
- package/build/dist/Server/Utils/AI/SRE/ConfidenceSignal.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.js +170 -31
- package/build/dist/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +22 -19
- package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionIdentity.js +97 -0
- package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionIdentity.js.map +1 -0
- package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.js +17 -15
- package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.js +178 -54
- package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/Insights/InsightStore.js +65 -16
- package/build/dist/Server/Utils/AI/SRE/Insights/InsightStore.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InstrumentationTaskTrigger.js +33 -9
- package/build/dist/Server/Utils/AI/SRE/InstrumentationTaskTrigger.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js +9 -5
- package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js +121 -41
- package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationSubjectLock.js +46 -0
- package/build/dist/Server/Utils/AI/SRE/InvestigationSubjectLock.js.map +1 -0
- package/build/dist/Server/Utils/AI/SRE/InvestigationTldr.js +303 -0
- package/build/dist/Server/Utils/AI/SRE/InvestigationTldr.js.map +1 -0
- package/build/dist/Server/Utils/AI/SRE/PostedRootCause.js +61 -14
- package/build/dist/Server/Utils/AI/SRE/PostedRootCause.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js +79 -1
- package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/ClusterConfig.js +21 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/ClusterConfig.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/InsertDedupContext.js.map +1 -1
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +1261 -0
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -0
- package/build/dist/Server/Utils/Dashboard/PublicDashboardViewConfig.js +47 -0
- package/build/dist/Server/Utils/Dashboard/PublicDashboardViewConfig.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/ConnectorRegistry.js +42 -0
- package/build/dist/Server/Utils/DataSource/ConnectorRegistry.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/Connectors/ClickHouseConnector.js +187 -0
- package/build/dist/Server/Utils/DataSource/Connectors/ClickHouseConnector.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/Connectors/ElasticsearchConnector.js +493 -0
- package/build/dist/Server/Utils/DataSource/Connectors/ElasticsearchConnector.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/Connectors/LokiConnector.js +399 -0
- package/build/dist/Server/Utils/DataSource/Connectors/LokiConnector.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/Connectors/MySqlConnector.js +195 -0
- package/build/dist/Server/Utils/DataSource/Connectors/MySqlConnector.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/Connectors/PostgresConnector.js +149 -0
- package/build/dist/Server/Utils/DataSource/Connectors/PostgresConnector.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/Connectors/PrometheusConnector.js +362 -0
- package/build/dist/Server/Utils/DataSource/Connectors/PrometheusConnector.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/Connectors/RestApiConnector.js +342 -0
- package/build/dist/Server/Utils/DataSource/Connectors/RestApiConnector.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/Connectors/SqlServerConnector.js +186 -0
- package/build/dist/Server/Utils/DataSource/Connectors/SqlServerConnector.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/EgressGuard.js +305 -0
- package/build/dist/Server/Utils/DataSource/EgressGuard.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/HttpFetch.js +134 -0
- package/build/dist/Server/Utils/DataSource/HttpFetch.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/SqlQueryGuard.js +331 -0
- package/build/dist/Server/Utils/DataSource/SqlQueryGuard.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/SqlResultShaper.js +280 -0
- package/build/dist/Server/Utils/DataSource/SqlResultShaper.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/Types.js +2 -0
- package/build/dist/Server/Utils/DataSource/Types.js.map +1 -0
- package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js +103 -9
- package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js.map +1 -1
- package/build/dist/Server/Utils/InProcessMemo.js +113 -0
- package/build/dist/Server/Utils/InProcessMemo.js.map +1 -0
- package/build/dist/Server/Utils/InstanceHealth/TelemetryIngestion.js +406 -0
- package/build/dist/Server/Utils/InstanceHealth/TelemetryIngestion.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js +20 -3
- package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js +43 -0
- package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +67 -0
- package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +14 -0
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
- package/build/dist/Server/Utils/PasswordHash.js +25 -4
- package/build/dist/Server/Utils/PasswordHash.js.map +1 -1
- package/build/dist/Server/Utils/Response.js +9 -1
- package/build/dist/Server/Utils/Response.js.map +1 -1
- package/build/dist/Server/Utils/SSRFProtection.js +192 -0
- package/build/dist/Server/Utils/SSRFProtection.js.map +1 -0
- package/build/dist/Server/Utils/StartServer.js +7 -0
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/StatusPageSubscriberWebhook.js +24 -0
- package/build/dist/Server/Utils/StatusPageSubscriberWebhook.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/CaptureSpan.js +25 -11
- package/build/dist/Server/Utils/Telemetry/CaptureSpan.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/IngestionTimestamp.js +32 -0
- package/build/dist/Server/Utils/Telemetry/IngestionTimestamp.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js +92 -5
- package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/Telemetry.js +92 -6
- package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry.js +93 -6
- package/build/dist/Server/Utils/Telemetry.js.map +1 -1
- package/build/dist/Server/Utils/VendorAssets.js +121 -0
- package/build/dist/Server/Utils/VendorAssets.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +25 -4
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js +5 -0
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
- package/build/dist/Types/AI/AIRunCodeFixRecommendation.js +18 -0
- package/build/dist/Types/AI/AIRunCodeFixRecommendation.js.map +1 -0
- package/build/dist/Types/AI/CodeFixTaskContext.js +23 -3
- package/build/dist/Types/AI/CodeFixTaskContext.js.map +1 -1
- package/build/dist/Types/AI/CodeFixTaskType.js +3 -3
- package/build/dist/Types/Analytics/RevenueEvent.js +26 -0
- package/build/dist/Types/Analytics/RevenueEvent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponentType.js +27 -0
- package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceChartComponent.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceChartComponent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceGaugeComponent.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceGaugeComponent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceTableComponent.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceTableComponent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceValueComponent.js +16 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceValueComponent.js.map +1 -0
- package/build/dist/Types/DataSource/DataSourceLimits.js +47 -0
- package/build/dist/Types/DataSource/DataSourceLimits.js.map +1 -0
- package/build/dist/Types/DataSource/DataSourceQueryConfig.js +10 -0
- package/build/dist/Types/DataSource/DataSourceQueryConfig.js.map +1 -0
- package/build/dist/Types/DataSource/DataSourceTableResult.js +14 -0
- package/build/dist/Types/DataSource/DataSourceTableResult.js.map +1 -0
- package/build/dist/Types/DataSource/DataSourceType.js +183 -0
- package/build/dist/Types/DataSource/DataSourceType.js.map +1 -0
- package/build/dist/Types/Date.js +38 -3
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/HashedString.js +4 -3
- package/build/dist/Types/HashedString.js.map +1 -1
- package/build/dist/Types/Monitor/CriteriaFilter.js +4 -0
- package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
- package/build/dist/Types/Monitor/DomainMonitor/DomainLookupMethod.js +24 -0
- package/build/dist/Types/Monitor/DomainMonitor/DomainLookupMethod.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +24 -7
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorMetricType.js +7 -0
- package/build/dist/Types/Monitor/MonitorMetricType.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStep.js +6 -1
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStepDomainMonitor.js +15 -0
- package/build/dist/Types/Monitor/MonitorStepDomainMonitor.js.map +1 -1
- package/build/dist/Types/Monitor/PortMonitor/PortMonitorTimings.js +2 -0
- package/build/dist/Types/Monitor/PortMonitor/PortMonitorTimings.js.map +1 -0
- package/build/dist/Types/Password.js +56 -0
- package/build/dist/Types/Password.js.map +1 -0
- package/build/dist/Types/Permission.js +65 -155
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/UserAuthenticationStatus.js +2 -0
- package/build/dist/Types/UserAuthenticationStatus.js.map +1 -0
- package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +2 -5
- package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
- package/build/dist/UI/Components/ErrorBoundary.js +427 -52
- package/build/dist/UI/Components/ErrorBoundary.js.map +1 -1
- package/build/dist/UI/Components/Feed/FeedItem.js +2 -2
- package/build/dist/UI/Components/Feed/FeedItem.js.map +1 -1
- package/build/dist/UI/Components/Icon/Icon.js +10 -4
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/UI/Components/Modal/ConfirmModal.js +1 -1
- package/build/dist/UI/Components/Modal/ConfirmModal.js.map +1 -1
- package/build/dist/UI/Components/Modal/Modal.js +178 -19
- package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
- package/build/dist/UI/Components/Modal/ModalFooter.js +3 -1
- package/build/dist/UI/Components/Modal/ModalFooter.js.map +1 -1
- package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +0 -12
- package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js +12 -2
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js.map +1 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenu.js +197 -66
- package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +7 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
- package/build/dist/UI/Components/StatusPage/ResourceGrid.js +119 -0
- package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -0
- package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js +354 -0
- package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js.map +1 -0
- package/build/dist/UI/Components/StatusPage/ResourceList.js +203 -0
- package/build/dist/UI/Components/StatusPage/ResourceList.js.map +1 -0
- package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js +1 -6
- package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js.map +1 -1
- package/build/dist/UI/Utils/DownloadFile.js +15 -8
- package/build/dist/UI/Utils/DownloadFile.js.map +1 -1
- package/build/dist/UI/Utils/ErrorSupportBundle.js +476 -0
- package/build/dist/UI/Utils/ErrorSupportBundle.js.map +1 -0
- package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +20 -15
- package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
- package/build/dist/UI/Utils/Project.js +3 -0
- package/build/dist/UI/Utils/Project.js.map +1 -1
- package/build/dist/UI/Utils/StatusPage.js +4 -4
- package/build/dist/UI/Utils/StatusPage.js.map +1 -1
- package/build/dist/UI/Utils/TestDataSource.js +39 -0
- package/build/dist/UI/Utils/TestDataSource.js.map +1 -0
- package/build/dist/UI/Utils/UseDashboardGridDnd.js +551 -0
- package/build/dist/UI/Utils/UseDashboardGridDnd.js.map +1 -0
- package/build/dist/Utils/Analytics.js +4 -0
- package/build/dist/Utils/Analytics.js.map +1 -1
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceChartComponent.js +79 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceChartComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceGaugeComponent.js +111 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceGaugeComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceShared.js +15 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceShared.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceTableComponent.js +62 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceTableComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceValueComponent.js +112 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceValueComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/Index.js +16 -0
- package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
- package/build/dist/Utils/Dashboard/DashboardViewConfig.js +42 -26
- package/build/dist/Utils/Dashboard/DashboardViewConfig.js.map +1 -1
- package/build/dist/Utils/Dashboard/GridLayout.js +338 -0
- package/build/dist/Utils/Dashboard/GridLayout.js.map +1 -0
- package/build/dist/Utils/DataSource/DataSourceQueryText.js +64 -0
- package/build/dist/Utils/DataSource/DataSourceQueryText.js.map +1 -0
- package/build/dist/Utils/DataSource/DataSourceValueReducer.js +124 -0
- package/build/dist/Utils/DataSource/DataSourceValueReducer.js.map +1 -0
- package/build/dist/Utils/Metrics/MetricResourceAttributeUtil.js +281 -0
- package/build/dist/Utils/Metrics/MetricResourceAttributeUtil.js.map +1 -0
- package/build/dist/Utils/Metrics/RecordingRuleExpression.js +19 -17
- package/build/dist/Utils/Metrics/RecordingRuleExpression.js.map +1 -1
- package/build/dist/Utils/Monitor/MonitorMetricType.js +38 -6
- package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
- package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js +8 -6
- package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js.map +1 -1
- package/build/dist/Utils/StatusPage/GroupHierarchyView.js +560 -0
- package/build/dist/Utils/StatusPage/GroupHierarchyView.js.map +1 -0
- package/build/dist/Utils/StatusPage/GroupNestingLayout.js +46 -0
- package/build/dist/Utils/StatusPage/GroupNestingLayout.js.map +1 -1
- package/build/dist/Utils/StatusPage/GroupTree.js +79 -34
- package/build/dist/Utils/StatusPage/GroupTree.js.map +1 -1
- package/build/dist/Utils/StatusPage/ResourceExplorer.js +519 -0
- package/build/dist/Utils/StatusPage/ResourceExplorer.js.map +1 -0
- package/build/dist/Utils/StatusPage/ResourceUptime.js +22 -1
- package/build/dist/Utils/StatusPage/ResourceUptime.js.map +1 -1
- package/build/dist/Utils/Telemetry/SavedViewTimeRange.js +79 -0
- package/build/dist/Utils/Telemetry/SavedViewTimeRange.js.map +1 -0
- package/build/dist/Utils/Telemetry/TelemetryQueryTimeRange.js +236 -0
- package/build/dist/Utils/Telemetry/TelemetryQueryTimeRange.js.map +1 -0
- package/jest.config.json +1 -0
- package/package.json +5 -1
- package/tsconfig.json +12 -10
|
@@ -0,0 +1,973 @@
|
|
|
1
|
+
import DatabaseConfig from "../../../Server/DatabaseConfig";
|
|
2
|
+
import { EncryptionSecret } from "../../../Server/EnvironmentConfig";
|
|
3
|
+
import MailService from "../../../Server/Services/MailService";
|
|
4
|
+
import UserService from "../../../Server/Services/UserService";
|
|
5
|
+
import UserSessionService from "../../../Server/Services/UserSessionService";
|
|
6
|
+
import logger from "../../../Server/Utils/Logger";
|
|
7
|
+
import User from "../../../Models/DatabaseModels/User";
|
|
8
|
+
import Hostname from "../../../Types/API/Hostname";
|
|
9
|
+
import HTTPErrorResponse from "../../../Types/API/HTTPErrorResponse";
|
|
10
|
+
import Protocol from "../../../Types/API/Protocol";
|
|
11
|
+
import OneUptimeDate from "../../../Types/Date";
|
|
12
|
+
import Email from "../../../Types/Email";
|
|
13
|
+
import EmailTemplateType from "../../../Types/Email/EmailTemplateType";
|
|
14
|
+
import BadDataException from "../../../Types/Exception/BadDataException";
|
|
15
|
+
import NotFoundException from "../../../Types/Exception/NotFoundException";
|
|
16
|
+
import HashedString from "../../../Types/HashedString";
|
|
17
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
18
|
+
import UserAuthenticationStatus from "../../../Types/UserAuthenticationStatus";
|
|
19
|
+
import { getJestSpyOn } from "../../Spy";
|
|
20
|
+
import {
|
|
21
|
+
afterEach,
|
|
22
|
+
beforeEach,
|
|
23
|
+
describe,
|
|
24
|
+
expect,
|
|
25
|
+
jest,
|
|
26
|
+
test,
|
|
27
|
+
} from "@jest/globals";
|
|
28
|
+
|
|
29
|
+
/*
|
|
30
|
+
* ---------------------------------------------------------------------------
|
|
31
|
+
* UserService.getAuthenticationStatus / setPassword / sendPasswordResetLink --
|
|
32
|
+
* the three methods behind Admin Dashboard > Users > (a user) > Authentication.
|
|
33
|
+
*
|
|
34
|
+
* These are the only place in the product where one human changes another
|
|
35
|
+
* human's credentials, so every invariant they carry is a security property
|
|
36
|
+
* rather than a nicety, and each one of them can break WITHOUT the page looking
|
|
37
|
+
* any different:
|
|
38
|
+
*
|
|
39
|
+
* - the status page reads `enableTwoFactorAuth`. `User` also has a
|
|
40
|
+
* `twoFactorAuthEnabled` column left over from the initial migration that
|
|
41
|
+
* nothing writes. Reading the wrong one renders "Two-factor auth: disabled"
|
|
42
|
+
* for a user who has it very much enabled -- the page still renders, the
|
|
43
|
+
* operator is just told the opposite of the truth.
|
|
44
|
+
* - setPassword hands the write path an UNHASHED HashedString on purpose.
|
|
45
|
+
* "Helpfully" pre-hashing it would store a digest that does not agree with
|
|
46
|
+
* the per-user salt the write path mints, and nothing fails until the user
|
|
47
|
+
* tries to log in.
|
|
48
|
+
* - sendPasswordResetLink stores only a SHA-256 of the token. Storing the
|
|
49
|
+
* token itself would still produce a working link, so no test that merely
|
|
50
|
+
* follows the link would notice; the difference only shows up in what a
|
|
51
|
+
* leaked database row is worth.
|
|
52
|
+
*
|
|
53
|
+
* Everything below stubs at this service's own boundary -- `findOneBy` and
|
|
54
|
+
* `updateOneById` on the UserService singleton, plus MailService and
|
|
55
|
+
* DatabaseConfig -- so the assertions are about what these three methods ASK
|
|
56
|
+
* for, which is exactly where the bugs above would live. No database is
|
|
57
|
+
* involved.
|
|
58
|
+
* ---------------------------------------------------------------------------
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
// The values the mocked DatabaseConfig reports, so the expected link is exact.
|
|
62
|
+
const TEST_HOSTNAME: string = "oneuptime.example.com";
|
|
63
|
+
const EXPECTED_ORIGIN: string = `http://${TEST_HOSTNAME}`;
|
|
64
|
+
|
|
65
|
+
type BuildUserFunction = (data: {
|
|
66
|
+
id: ObjectID;
|
|
67
|
+
email?: string | undefined;
|
|
68
|
+
password?: HashedString | undefined;
|
|
69
|
+
isEmailVerified?: boolean | undefined;
|
|
70
|
+
enableTwoFactorAuth?: boolean | undefined;
|
|
71
|
+
twoFactorAuthEnabled?: boolean | undefined;
|
|
72
|
+
resetPasswordToken?: string | undefined;
|
|
73
|
+
resetPasswordExpires?: Date | undefined;
|
|
74
|
+
}) => User;
|
|
75
|
+
|
|
76
|
+
const buildUser: BuildUserFunction = (data: {
|
|
77
|
+
id: ObjectID;
|
|
78
|
+
email?: string | undefined;
|
|
79
|
+
password?: HashedString | undefined;
|
|
80
|
+
isEmailVerified?: boolean | undefined;
|
|
81
|
+
enableTwoFactorAuth?: boolean | undefined;
|
|
82
|
+
twoFactorAuthEnabled?: boolean | undefined;
|
|
83
|
+
resetPasswordToken?: string | undefined;
|
|
84
|
+
resetPasswordExpires?: Date | undefined;
|
|
85
|
+
}): User => {
|
|
86
|
+
const user: User = new User();
|
|
87
|
+
user.id = data.id;
|
|
88
|
+
|
|
89
|
+
if (data.email !== undefined) {
|
|
90
|
+
user.email = new Email(data.email);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/*
|
|
94
|
+
* Assigned only when supplied, so an omitted field leaves the column
|
|
95
|
+
* genuinely absent -- the same shape a `select` that did not ask for it
|
|
96
|
+
* produces -- rather than explicitly undefined.
|
|
97
|
+
*/
|
|
98
|
+
if (data.password !== undefined) {
|
|
99
|
+
user.password = data.password;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (data.isEmailVerified !== undefined) {
|
|
103
|
+
user.isEmailVerified = data.isEmailVerified;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (data.enableTwoFactorAuth !== undefined) {
|
|
107
|
+
user.enableTwoFactorAuth = data.enableTwoFactorAuth;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (data.twoFactorAuthEnabled !== undefined) {
|
|
111
|
+
user.twoFactorAuthEnabled = data.twoFactorAuthEnabled;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (data.resetPasswordToken !== undefined) {
|
|
115
|
+
user.resetPasswordToken = data.resetPasswordToken;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (data.resetPasswordExpires !== undefined) {
|
|
119
|
+
user.resetPasswordExpires = data.resetPasswordExpires;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return user;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
describe("UserService -- master-admin authentication management", () => {
|
|
126
|
+
let userId: ObjectID;
|
|
127
|
+
let findOneBySpy: any;
|
|
128
|
+
let updateOneByIdSpy: any;
|
|
129
|
+
let sendMailSpy: any;
|
|
130
|
+
let revokeSessionsSpy: any;
|
|
131
|
+
|
|
132
|
+
beforeEach(() => {
|
|
133
|
+
jest.restoreAllMocks();
|
|
134
|
+
|
|
135
|
+
userId = ObjectID.generate();
|
|
136
|
+
|
|
137
|
+
findOneBySpy = getJestSpyOn(UserService, "findOneBy").mockImplementation(
|
|
138
|
+
async (): Promise<User | null> => {
|
|
139
|
+
return null;
|
|
140
|
+
},
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
updateOneByIdSpy = getJestSpyOn(
|
|
144
|
+
UserService,
|
|
145
|
+
"updateOneById",
|
|
146
|
+
).mockImplementation(async (): Promise<void> => {
|
|
147
|
+
return undefined;
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
sendMailSpy = getJestSpyOn(MailService, "sendMail").mockImplementation(
|
|
151
|
+
async (): Promise<any> => {
|
|
152
|
+
return {};
|
|
153
|
+
},
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
/*
|
|
157
|
+
* Revocation belongs to the update hook, not to setPassword. It is stubbed
|
|
158
|
+
* here purely so the "was it called?" assertion cannot accidentally reach a
|
|
159
|
+
* real session table.
|
|
160
|
+
*/
|
|
161
|
+
revokeSessionsSpy = getJestSpyOn(
|
|
162
|
+
UserSessionService,
|
|
163
|
+
"revokeAllSessionsByUserId",
|
|
164
|
+
).mockImplementation(async (): Promise<void> => {
|
|
165
|
+
return undefined;
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
getJestSpyOn(DatabaseConfig, "getHost").mockImplementation(
|
|
169
|
+
async (): Promise<Hostname> => {
|
|
170
|
+
return new Hostname(TEST_HOSTNAME);
|
|
171
|
+
},
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
getJestSpyOn(DatabaseConfig, "getHttpProtocol").mockImplementation(
|
|
175
|
+
async (): Promise<Protocol> => {
|
|
176
|
+
return Protocol.HTTP;
|
|
177
|
+
},
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
getJestSpyOn(logger, "info").mockImplementation((): void => {
|
|
181
|
+
return undefined;
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
/*
|
|
185
|
+
* The deliberate-failure cases below hand a rejection to the CaptureSpan
|
|
186
|
+
* decorator, which records it. That is correct behaviour, and it is also a
|
|
187
|
+
* stack trace on stderr for a test that passed -- silenced so a genuine
|
|
188
|
+
* error in this suite's output is still worth reading.
|
|
189
|
+
*/
|
|
190
|
+
getJestSpyOn(logger, "error").mockImplementation((): void => {
|
|
191
|
+
return undefined;
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
afterEach(() => {
|
|
196
|
+
jest.restoreAllMocks();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
type ResolveUserFunction = (user: User | null) => void;
|
|
200
|
+
|
|
201
|
+
// Makes the next (and every subsequent) findOneBy return this row.
|
|
202
|
+
const resolveUser: ResolveUserFunction = (user: User | null): void => {
|
|
203
|
+
findOneBySpy.mockImplementation(async (): Promise<User | null> => {
|
|
204
|
+
return user;
|
|
205
|
+
});
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
describe("getAuthenticationStatus", () => {
|
|
209
|
+
test("throws NotFoundException when there is no such user", async () => {
|
|
210
|
+
/*
|
|
211
|
+
* Returning a status object full of `false` for a user id that does not
|
|
212
|
+
* exist would render an ordinary-looking page describing nobody -- an
|
|
213
|
+
* operator acting on a mistyped id has to be told, not shown defaults.
|
|
214
|
+
*/
|
|
215
|
+
resolveUser(null);
|
|
216
|
+
|
|
217
|
+
await expect(UserService.getAuthenticationStatus(userId)).rejects.toThrow(
|
|
218
|
+
NotFoundException,
|
|
219
|
+
);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test("hasPassword is true when the password column is set", async () => {
|
|
223
|
+
/*
|
|
224
|
+
* The column carries a scrypt digest read out of the row, so the flag has
|
|
225
|
+
* to be derived from presence, not from any particular shape of value.
|
|
226
|
+
*/
|
|
227
|
+
resolveUser(
|
|
228
|
+
buildUser({
|
|
229
|
+
id: userId,
|
|
230
|
+
password: new HashedString("a-stored-scrypt-digest", true),
|
|
231
|
+
}),
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
const status: UserAuthenticationStatus =
|
|
235
|
+
await UserService.getAuthenticationStatus(userId);
|
|
236
|
+
|
|
237
|
+
expect(status.hasPassword).toBe(true);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test("hasPassword is false for an SSO-only user with no password", async () => {
|
|
241
|
+
/*
|
|
242
|
+
* This is the fact the Authentication page exists to show: a user with no
|
|
243
|
+
* password cannot be helped by "send them a reset link", they need one
|
|
244
|
+
* set. Reporting true here would send the operator down the wrong path.
|
|
245
|
+
*/
|
|
246
|
+
resolveUser(buildUser({ id: userId }));
|
|
247
|
+
|
|
248
|
+
const status: UserAuthenticationStatus =
|
|
249
|
+
await UserService.getAuthenticationStatus(userId);
|
|
250
|
+
|
|
251
|
+
expect(status.hasPassword).toBe(false);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test("isEmailVerified mirrors the column", async () => {
|
|
255
|
+
resolveUser(buildUser({ id: userId, isEmailVerified: true }));
|
|
256
|
+
|
|
257
|
+
const verified: UserAuthenticationStatus =
|
|
258
|
+
await UserService.getAuthenticationStatus(userId);
|
|
259
|
+
expect(verified.isEmailVerified).toBe(true);
|
|
260
|
+
|
|
261
|
+
resolveUser(buildUser({ id: userId, isEmailVerified: false }));
|
|
262
|
+
|
|
263
|
+
const unverified: UserAuthenticationStatus =
|
|
264
|
+
await UserService.getAuthenticationStatus(userId);
|
|
265
|
+
expect(unverified.isEmailVerified).toBe(false);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test("reads enableTwoFactorAuth, not the vestigial twoFactorAuthEnabled column", async () => {
|
|
269
|
+
/*
|
|
270
|
+
* THE SUBTLE ONE. `User` declares BOTH `enableTwoFactorAuth` and
|
|
271
|
+
* `twoFactorAuthEnabled`. Only `enableTwoFactorAuth` is the one login
|
|
272
|
+
* consults and the one the rest of the product writes;
|
|
273
|
+
* `twoFactorAuthEnabled` is a leftover that nothing maintains.
|
|
274
|
+
*
|
|
275
|
+
* This row is the disagreeing case: 2FA is genuinely ON, and the dead
|
|
276
|
+
* column says off. Reading the dead column would compile, would render,
|
|
277
|
+
* and would tell an operator "two-factor auth is disabled" about a user
|
|
278
|
+
* who cannot get in without their authenticator -- which is exactly the
|
|
279
|
+
* moment the operator is deciding whether to reset it.
|
|
280
|
+
*/
|
|
281
|
+
resolveUser(
|
|
282
|
+
buildUser({
|
|
283
|
+
id: userId,
|
|
284
|
+
enableTwoFactorAuth: true,
|
|
285
|
+
twoFactorAuthEnabled: false,
|
|
286
|
+
}),
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
const status: UserAuthenticationStatus =
|
|
290
|
+
await UserService.getAuthenticationStatus(userId);
|
|
291
|
+
|
|
292
|
+
expect(status.isTwoFactorAuthEnabled).toBe(true);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
test("does not report 2FA on just because the vestigial column is set", async () => {
|
|
296
|
+
/*
|
|
297
|
+
* The mirror of the case above, so the test cannot pass by hard-coding
|
|
298
|
+
* true: the dead column says on, the live one says off, and the answer
|
|
299
|
+
* has to follow the live one.
|
|
300
|
+
*/
|
|
301
|
+
resolveUser(
|
|
302
|
+
buildUser({
|
|
303
|
+
id: userId,
|
|
304
|
+
enableTwoFactorAuth: false,
|
|
305
|
+
twoFactorAuthEnabled: true,
|
|
306
|
+
}),
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
const status: UserAuthenticationStatus =
|
|
310
|
+
await UserService.getAuthenticationStatus(userId);
|
|
311
|
+
|
|
312
|
+
expect(status.isTwoFactorAuthEnabled).toBe(false);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
test("hasPendingPasswordResetLink is false when no token has ever been minted", async () => {
|
|
316
|
+
resolveUser(buildUser({ id: userId }));
|
|
317
|
+
|
|
318
|
+
const status: UserAuthenticationStatus =
|
|
319
|
+
await UserService.getAuthenticationStatus(userId);
|
|
320
|
+
|
|
321
|
+
expect(status.hasPendingPasswordResetLink).toBe(false);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
test("hasPendingPasswordResetLink is false once the token has expired", async () => {
|
|
325
|
+
/*
|
|
326
|
+
* Nothing clears `resetPasswordToken` when it lapses -- the column keeps
|
|
327
|
+
* the last token until something overwrites it. Treating "column is set"
|
|
328
|
+
* as "link is pending" would therefore report a live link for every user
|
|
329
|
+
* who ever requested one, and /reset-password would refuse it.
|
|
330
|
+
*/
|
|
331
|
+
resolveUser(
|
|
332
|
+
buildUser({
|
|
333
|
+
id: userId,
|
|
334
|
+
resetPasswordToken: "a-hashed-token",
|
|
335
|
+
resetPasswordExpires: OneUptimeDate.getSomeHoursAgo(1),
|
|
336
|
+
}),
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
const status: UserAuthenticationStatus =
|
|
340
|
+
await UserService.getAuthenticationStatus(userId);
|
|
341
|
+
|
|
342
|
+
expect(status.hasPendingPasswordResetLink).toBe(false);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
test("hasPendingPasswordResetLink is false for a token with no expiry at all", async () => {
|
|
346
|
+
/*
|
|
347
|
+
* A token with a null expiry is not an eternal link -- /reset-password
|
|
348
|
+
* requires an unexpired expiry to redeem it, so a row in this shape has
|
|
349
|
+
* nothing redeemable on it.
|
|
350
|
+
*/
|
|
351
|
+
resolveUser(
|
|
352
|
+
buildUser({
|
|
353
|
+
id: userId,
|
|
354
|
+
resetPasswordToken: "a-hashed-token",
|
|
355
|
+
}),
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
const status: UserAuthenticationStatus =
|
|
359
|
+
await UserService.getAuthenticationStatus(userId);
|
|
360
|
+
|
|
361
|
+
expect(status.hasPendingPasswordResetLink).toBe(false);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
test("hasPendingPasswordResetLink is true for an unexpired token", async () => {
|
|
365
|
+
resolveUser(
|
|
366
|
+
buildUser({
|
|
367
|
+
id: userId,
|
|
368
|
+
resetPasswordToken: "a-hashed-token",
|
|
369
|
+
resetPasswordExpires: OneUptimeDate.getSomeHoursAfter(1),
|
|
370
|
+
}),
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
const status: UserAuthenticationStatus =
|
|
374
|
+
await UserService.getAuthenticationStatus(userId);
|
|
375
|
+
|
|
376
|
+
expect(status.hasPendingPasswordResetLink).toBe(true);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test("returns only the four booleans -- never the password digest itself", async () => {
|
|
380
|
+
/*
|
|
381
|
+
* The whole reason this is a DTO rather than a selected row: the caller
|
|
382
|
+
* is a master admin, whose props bypass column read permissions, so
|
|
383
|
+
* handing the model back would put every user's password digest (and the
|
|
384
|
+
* hash of their outstanding reset token) into a browser's network tab.
|
|
385
|
+
*
|
|
386
|
+
* Asserting the exact key set rather than "password is undefined" is
|
|
387
|
+
* deliberate -- a future field added to the row would otherwise ride
|
|
388
|
+
* along unnoticed.
|
|
389
|
+
*/
|
|
390
|
+
const digest: string = "a-stored-scrypt-digest";
|
|
391
|
+
|
|
392
|
+
resolveUser(
|
|
393
|
+
buildUser({
|
|
394
|
+
id: userId,
|
|
395
|
+
password: new HashedString(digest, true),
|
|
396
|
+
resetPasswordToken: "a-hashed-token",
|
|
397
|
+
resetPasswordExpires: OneUptimeDate.getSomeHoursAfter(1),
|
|
398
|
+
}),
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
const status: UserAuthenticationStatus =
|
|
402
|
+
await UserService.getAuthenticationStatus(userId);
|
|
403
|
+
|
|
404
|
+
expect(Object.keys(status).sort()).toEqual([
|
|
405
|
+
"hasPassword",
|
|
406
|
+
"hasPendingPasswordResetLink",
|
|
407
|
+
"isEmailVerified",
|
|
408
|
+
"isTwoFactorAuthEnabled",
|
|
409
|
+
]);
|
|
410
|
+
|
|
411
|
+
const serialized: string = JSON.stringify(status);
|
|
412
|
+
expect(serialized).not.toContain(digest);
|
|
413
|
+
expect(serialized).not.toContain("a-hashed-token");
|
|
414
|
+
|
|
415
|
+
for (const value of Object.values(status)) {
|
|
416
|
+
expect(typeof value).toBe("boolean");
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
test("selects exactly the columns the four booleans are derived from", async () => {
|
|
421
|
+
/*
|
|
422
|
+
* Pinned literally because both halves matter. Dropping a column here
|
|
423
|
+
* turns its boolean permanently false rather than raising anything --
|
|
424
|
+
* losing `enableTwoFactorAuth` would silently report 2FA off for
|
|
425
|
+
* everyone. Adding one widens what a root read pulls out of the row for
|
|
426
|
+
* no reason.
|
|
427
|
+
*/
|
|
428
|
+
resolveUser(buildUser({ id: userId }));
|
|
429
|
+
|
|
430
|
+
await UserService.getAuthenticationStatus(userId);
|
|
431
|
+
|
|
432
|
+
const call: any = findOneBySpy.mock.calls[0][0];
|
|
433
|
+
|
|
434
|
+
expect(call.select).toEqual({
|
|
435
|
+
_id: true,
|
|
436
|
+
password: true,
|
|
437
|
+
isEmailVerified: true,
|
|
438
|
+
enableTwoFactorAuth: true,
|
|
439
|
+
resetPasswordToken: true,
|
|
440
|
+
resetPasswordExpires: true,
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
expect(call.query).toEqual({ _id: userId });
|
|
444
|
+
expect(call.props).toEqual({ isRoot: true });
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
describe("setPassword", () => {
|
|
449
|
+
const VALID_PASSWORD: string = "a-perfectly-fine-password";
|
|
450
|
+
|
|
451
|
+
type ExpectRejectedBeforeAnyQueryFunction = (
|
|
452
|
+
password: string,
|
|
453
|
+
) => Promise<void>;
|
|
454
|
+
|
|
455
|
+
/*
|
|
456
|
+
* Validating FIRST is the point of the ordering, not an accident of it.
|
|
457
|
+
* Every OneUptime password form enforces the length rule in the browser;
|
|
458
|
+
* an admin API is precisely the caller that is not a browser. A guard
|
|
459
|
+
* placed after the read would still reject the value, but only after the
|
|
460
|
+
* request had already spent a query -- and, more importantly, the shape
|
|
461
|
+
* "read the row, then decide" is the one that invites a later edit to slip
|
|
462
|
+
* an update in between.
|
|
463
|
+
*/
|
|
464
|
+
const expectRejectedBeforeAnyQuery: ExpectRejectedBeforeAnyQueryFunction =
|
|
465
|
+
async (password: string): Promise<void> => {
|
|
466
|
+
// A row is available on purpose: nothing may go looking for it.
|
|
467
|
+
resolveUser(buildUser({ id: userId, email: "user@example.com" }));
|
|
468
|
+
|
|
469
|
+
await expect(
|
|
470
|
+
UserService.setPassword({ userId: userId, password: password }),
|
|
471
|
+
).rejects.toThrow(BadDataException);
|
|
472
|
+
|
|
473
|
+
expect(findOneBySpy).not.toHaveBeenCalled();
|
|
474
|
+
expect(updateOneByIdSpy).not.toHaveBeenCalled();
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
test("rejects a password shorter than the minimum, before touching the database", async () => {
|
|
478
|
+
await expectRejectedBeforeAnyQuery("short");
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
test("rejects a password that is only whitespace, before touching the database", async () => {
|
|
482
|
+
/*
|
|
483
|
+
* Long enough to pass the length rule, and still not a password. It is
|
|
484
|
+
* rejected rather than trimmed on purpose -- trimming would store
|
|
485
|
+
* something other than what was typed.
|
|
486
|
+
*/
|
|
487
|
+
await expectRejectedBeforeAnyQuery(" ");
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
test("rejects an empty password, before touching the database", async () => {
|
|
491
|
+
await expectRejectedBeforeAnyQuery("");
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
test("accepts a password of exactly the minimum length", async () => {
|
|
495
|
+
/*
|
|
496
|
+
* Guards the boundary from the off-by-one direction: a `<=` here would
|
|
497
|
+
* start rejecting passwords that every existing OneUptime form accepts.
|
|
498
|
+
*/
|
|
499
|
+
resolveUser(buildUser({ id: userId, email: "user@example.com" }));
|
|
500
|
+
|
|
501
|
+
await UserService.setPassword({ userId: userId, password: "abcdef" });
|
|
502
|
+
|
|
503
|
+
expect(updateOneByIdSpy).toHaveBeenCalledTimes(1);
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
test("throws NotFoundException for a user that does not exist, and updates nothing", async () => {
|
|
507
|
+
resolveUser(null);
|
|
508
|
+
|
|
509
|
+
await expect(
|
|
510
|
+
UserService.setPassword({
|
|
511
|
+
userId: userId,
|
|
512
|
+
password: VALID_PASSWORD,
|
|
513
|
+
}),
|
|
514
|
+
).rejects.toThrow(NotFoundException);
|
|
515
|
+
|
|
516
|
+
expect(updateOneByIdSpy).not.toHaveBeenCalled();
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
test("hands the write path the PLAINTEXT wrapped in an unhashed HashedString", async () => {
|
|
520
|
+
/*
|
|
521
|
+
* THE CENTRAL ASSERTION OF THIS FILE.
|
|
522
|
+
*
|
|
523
|
+
* `sanitizeCreateOrUpdate` is what mints a fresh per-user salt and runs
|
|
524
|
+
* scrypt, and it only does that for a HashedString whose isValueHashed()
|
|
525
|
+
* is false. Passing a bare string, or "helpfully" pre-hashing here,
|
|
526
|
+
* strands the row: the stored digest and the row's salt stop agreeing
|
|
527
|
+
* with each other.
|
|
528
|
+
*
|
|
529
|
+
* Nothing about that failure is visible at the time. The update commits,
|
|
530
|
+
* the admin page says the password was set, the operator tells the user
|
|
531
|
+
* their new password -- and the account is locked out at the next login
|
|
532
|
+
* attempt, with no error anywhere pointing back at this line.
|
|
533
|
+
*/
|
|
534
|
+
resolveUser(buildUser({ id: userId, email: "user@example.com" }));
|
|
535
|
+
|
|
536
|
+
await UserService.setPassword({
|
|
537
|
+
userId: userId,
|
|
538
|
+
password: VALID_PASSWORD,
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
const call: any = updateOneByIdSpy.mock.calls[0][0];
|
|
542
|
+
const password: any = call.data.password;
|
|
543
|
+
|
|
544
|
+
expect(password).toBeInstanceOf(HashedString);
|
|
545
|
+
expect(password.isValueHashed()).toBe(false);
|
|
546
|
+
expect(password.toString()).toBe(VALID_PASSWORD);
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
test("clears any outstanding reset link in the SAME update", async () => {
|
|
550
|
+
/*
|
|
551
|
+
* A link mailed five minutes before the admin intervened must not still
|
|
552
|
+
* be redeemable afterwards -- whoever holds it could set the password
|
|
553
|
+
* again, undoing the intervention. Doing it as a second statement would
|
|
554
|
+
* leave a window where the new password is live and the old link still
|
|
555
|
+
* works; doing it in the same update means there is no such window.
|
|
556
|
+
*/
|
|
557
|
+
resolveUser(buildUser({ id: userId, email: "user@example.com" }));
|
|
558
|
+
|
|
559
|
+
await UserService.setPassword({
|
|
560
|
+
userId: userId,
|
|
561
|
+
password: VALID_PASSWORD,
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
expect(updateOneByIdSpy).toHaveBeenCalledTimes(1);
|
|
565
|
+
|
|
566
|
+
const data: any = updateOneByIdSpy.mock.calls[0][0].data;
|
|
567
|
+
|
|
568
|
+
// Present as keys, or the column is left untouched rather than cleared.
|
|
569
|
+
expect(Object.keys(data)).toContain("resetPasswordToken");
|
|
570
|
+
expect(Object.keys(data)).toContain("resetPasswordExpires");
|
|
571
|
+
|
|
572
|
+
expect(data.resetPasswordToken).toBeFalsy();
|
|
573
|
+
expect(data.resetPasswordExpires).toBeFalsy();
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
test("updates the row that was found, as root, WITHOUT ignoring hooks", async () => {
|
|
577
|
+
/*
|
|
578
|
+
* `ignoreHooks: true` would make this update look identical and behave
|
|
579
|
+
* completely differently: `onUpdateSuccess` is what revokes every session
|
|
580
|
+
* the user has open and mails them "Password Changed.". Skipping it would
|
|
581
|
+
* leave an attacker's stolen session alive across the very password
|
|
582
|
+
* change meant to kill it, and tell the user nothing.
|
|
583
|
+
*/
|
|
584
|
+
resolveUser(buildUser({ id: userId, email: "user@example.com" }));
|
|
585
|
+
|
|
586
|
+
await UserService.setPassword({
|
|
587
|
+
userId: userId,
|
|
588
|
+
password: VALID_PASSWORD,
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
const call: any = updateOneByIdSpy.mock.calls[0][0];
|
|
592
|
+
|
|
593
|
+
expect(call.id.toString()).toBe(userId.toString());
|
|
594
|
+
expect(call.props.isRoot).toBe(true);
|
|
595
|
+
expect(call.props.ignoreHooks).toBeFalsy();
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
test("does not revoke sessions or send mail itself", async () => {
|
|
599
|
+
/*
|
|
600
|
+
* The hook already does both. Doing them here as well would not be
|
|
601
|
+
* harmlessly redundant -- the user would receive two identical
|
|
602
|
+
* "Password Changed." emails from one admin action, which reads as a
|
|
603
|
+
* second, unexplained change to their account.
|
|
604
|
+
*/
|
|
605
|
+
resolveUser(buildUser({ id: userId, email: "user@example.com" }));
|
|
606
|
+
|
|
607
|
+
await UserService.setPassword({
|
|
608
|
+
userId: userId,
|
|
609
|
+
password: VALID_PASSWORD,
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
expect(revokeSessionsSpy).not.toHaveBeenCalled();
|
|
613
|
+
expect(sendMailSpy).not.toHaveBeenCalled();
|
|
614
|
+
});
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
describe("sendPasswordResetLink", () => {
|
|
618
|
+
type SentLinkFunction = () => string;
|
|
619
|
+
|
|
620
|
+
// The URL the operator's click actually put in the user's inbox.
|
|
621
|
+
const sentLink: SentLinkFunction = (): string => {
|
|
622
|
+
const mail: any = sendMailSpy.mock.calls[0][0];
|
|
623
|
+
return mail.vars.tokenVerifyUrl as string;
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
type StoredTokenFunction = () => string;
|
|
627
|
+
|
|
628
|
+
// What the row now holds in resetPasswordToken.
|
|
629
|
+
const storedToken: StoredTokenFunction = (): string => {
|
|
630
|
+
return updateOneByIdSpy.mock.calls[0][0].data
|
|
631
|
+
.resetPasswordToken as string;
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
test("throws NotFoundException for a user that does not exist, and sends no mail", async () => {
|
|
635
|
+
resolveUser(null);
|
|
636
|
+
|
|
637
|
+
await expect(
|
|
638
|
+
UserService.sendPasswordResetLink({ userId: userId }),
|
|
639
|
+
).rejects.toThrow(NotFoundException);
|
|
640
|
+
|
|
641
|
+
expect(updateOneByIdSpy).not.toHaveBeenCalled();
|
|
642
|
+
expect(sendMailSpy).not.toHaveBeenCalled();
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
test("stores a hash of the token and mails the token itself", async () => {
|
|
646
|
+
/*
|
|
647
|
+
* The property that makes a leaked database row worthless. What is
|
|
648
|
+
* written to `resetPasswordToken` must NOT be the string in the email --
|
|
649
|
+
* if it were, anyone with read access to the User table (a backup, a
|
|
650
|
+
* replica, a support query) could mint themselves a working reset link
|
|
651
|
+
* for any account without touching the mail system at all.
|
|
652
|
+
*
|
|
653
|
+
* Both halves are asserted, because either alone is satisfiable by a
|
|
654
|
+
* bug: "they differ" alone passes if the stored value is unrelated
|
|
655
|
+
* garbage that /reset-password could never match, and "it hashes to the
|
|
656
|
+
* stored value" is only meaningful once we know they are not the same
|
|
657
|
+
* string to begin with.
|
|
658
|
+
*
|
|
659
|
+
* The hash is unsalted on purpose -- /reset-password looks the row up BY
|
|
660
|
+
* this digest, which a per-row salt would make impossible.
|
|
661
|
+
*/
|
|
662
|
+
resolveUser(buildUser({ id: userId, email: "user@example.com" }));
|
|
663
|
+
|
|
664
|
+
await UserService.sendPasswordResetLink({ userId: userId });
|
|
665
|
+
|
|
666
|
+
const stored: string = storedToken();
|
|
667
|
+
const tokenInEmail: string = sentLink().split("/reset-password/")[1]!;
|
|
668
|
+
|
|
669
|
+
expect(tokenInEmail).toBeTruthy();
|
|
670
|
+
expect(stored).toBeTruthy();
|
|
671
|
+
expect(stored).not.toBe(tokenInEmail);
|
|
672
|
+
|
|
673
|
+
expect(await HashedString.hashValue(tokenInEmail, EncryptionSecret)).toBe(
|
|
674
|
+
stored,
|
|
675
|
+
);
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
test("mints a different token on every send", async () => {
|
|
679
|
+
/*
|
|
680
|
+
* A token derived from anything stable about the user -- their id, their
|
|
681
|
+
* email -- would make yesterday's expired link valid again the moment a
|
|
682
|
+
* new one is issued.
|
|
683
|
+
*/
|
|
684
|
+
resolveUser(buildUser({ id: userId, email: "user@example.com" }));
|
|
685
|
+
|
|
686
|
+
await UserService.sendPasswordResetLink({ userId: userId });
|
|
687
|
+
await UserService.sendPasswordResetLink({ userId: userId });
|
|
688
|
+
|
|
689
|
+
const first: string = updateOneByIdSpy.mock.calls[0][0].data
|
|
690
|
+
.resetPasswordToken as string;
|
|
691
|
+
const second: string = updateOneByIdSpy.mock.calls[1][0].data
|
|
692
|
+
.resetPasswordToken as string;
|
|
693
|
+
|
|
694
|
+
expect(first).not.toBe(second);
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
test("the link points at the ordinary /accounts/reset-password page", async () => {
|
|
698
|
+
/*
|
|
699
|
+
* There is deliberately no admin-specific redemption route: the link an
|
|
700
|
+
* operator sends is redeemed by exactly the page the public
|
|
701
|
+
* /forgot-password flow uses. A different path here would mean a second
|
|
702
|
+
* code path capable of setting passwords, which is the thing this feature
|
|
703
|
+
* was careful not to create.
|
|
704
|
+
*/
|
|
705
|
+
resolveUser(buildUser({ id: userId, email: "user@example.com" }));
|
|
706
|
+
|
|
707
|
+
await UserService.sendPasswordResetLink({ userId: userId });
|
|
708
|
+
|
|
709
|
+
const link: string = sentLink();
|
|
710
|
+
const token: string = link.split("/reset-password/")[1]!;
|
|
711
|
+
|
|
712
|
+
expect(link).toBe(`${EXPECTED_ORIGIN}/accounts/reset-password/${token}`);
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
test("expires the link roughly 24 hours out", async () => {
|
|
716
|
+
/*
|
|
717
|
+
* In the future, or the link is dead on arrival and the operator is told
|
|
718
|
+
* it was sent. Bounded above, or a mis-set expiry turns one admin action
|
|
719
|
+
* into a password-reset link that stays redeemable for months.
|
|
720
|
+
*/
|
|
721
|
+
resolveUser(buildUser({ id: userId, email: "user@example.com" }));
|
|
722
|
+
|
|
723
|
+
const before: Date = OneUptimeDate.getCurrentDate();
|
|
724
|
+
|
|
725
|
+
await UserService.sendPasswordResetLink({ userId: userId });
|
|
726
|
+
|
|
727
|
+
const expires: Date = updateOneByIdSpy.mock.calls[0][0].data
|
|
728
|
+
.resetPasswordExpires as Date;
|
|
729
|
+
|
|
730
|
+
expect(expires).toBeInstanceOf(Date);
|
|
731
|
+
expect(OneUptimeDate.hasExpired(expires)).toBe(false);
|
|
732
|
+
|
|
733
|
+
const hoursOut: number = OneUptimeDate.getHoursBetweenTwoDates(
|
|
734
|
+
before,
|
|
735
|
+
expires,
|
|
736
|
+
);
|
|
737
|
+
|
|
738
|
+
expect(hoursOut).toBeGreaterThanOrEqual(23);
|
|
739
|
+
expect(hoursOut).toBeLessThanOrEqual(25);
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
test("writes the token as root against the requested user", async () => {
|
|
743
|
+
resolveUser(buildUser({ id: userId, email: "user@example.com" }));
|
|
744
|
+
|
|
745
|
+
await UserService.sendPasswordResetLink({ userId: userId });
|
|
746
|
+
|
|
747
|
+
const call: any = updateOneByIdSpy.mock.calls[0][0];
|
|
748
|
+
|
|
749
|
+
expect(call.id.toString()).toBe(userId.toString());
|
|
750
|
+
expect(call.props.isRoot).toBe(true);
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
test("sends the ForgotPassword template to the user's own address", async () => {
|
|
754
|
+
/*
|
|
755
|
+
* The template is what renders the link into something clickable; a
|
|
756
|
+
* template type that does not reference `tokenVerifyUrl` would mail a
|
|
757
|
+
* cheerful message with no link in it, and this method would still
|
|
758
|
+
* report success.
|
|
759
|
+
*/
|
|
760
|
+
resolveUser(buildUser({ id: userId, email: "user@example.com" }));
|
|
761
|
+
|
|
762
|
+
await UserService.sendPasswordResetLink({ userId: userId });
|
|
763
|
+
|
|
764
|
+
const mail: any = sendMailSpy.mock.calls[0][0];
|
|
765
|
+
|
|
766
|
+
expect(mail.templateType).toBe(EmailTemplateType.ForgotPassword);
|
|
767
|
+
expect(mail.toEmail.toString()).toBe("user@example.com");
|
|
768
|
+
|
|
769
|
+
// Routeless URLs render with a trailing slash; the template links it as-is.
|
|
770
|
+
expect(mail.vars.homeURL).toBe(`${EXPECTED_ORIGIN}/`);
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
test("returns the address the link went to", async () => {
|
|
774
|
+
/*
|
|
775
|
+
* The operator pressed a button on a page identified by user id, not by
|
|
776
|
+
* email. Returning the address is what lets the UI say WHERE the link
|
|
777
|
+
* went, so a link sent to a stale address is noticed immediately rather
|
|
778
|
+
* than becoming "the user says they never got it".
|
|
779
|
+
*/
|
|
780
|
+
resolveUser(buildUser({ id: userId, email: "user@example.com" }));
|
|
781
|
+
|
|
782
|
+
const email: Email = await UserService.sendPasswordResetLink({
|
|
783
|
+
userId: userId,
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
expect(email.toString()).toBe("user@example.com");
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
test("a mail failure propagates instead of being reported as sent", async () => {
|
|
790
|
+
/*
|
|
791
|
+
* Most MailService calls in UserService are deliberately not awaited --
|
|
792
|
+
* a signup should not fail because SMTP is down. This one is the
|
|
793
|
+
* exception, and the exception is the whole point: a human is standing in
|
|
794
|
+
* front of the page waiting to tell someone "check your email".
|
|
795
|
+
* Swallowing the error here leaves them waiting for a link that was never
|
|
796
|
+
* sent.
|
|
797
|
+
*/
|
|
798
|
+
resolveUser(buildUser({ id: userId, email: "user@example.com" }));
|
|
799
|
+
|
|
800
|
+
sendMailSpy.mockImplementation(async (): Promise<never> => {
|
|
801
|
+
throw new Error("SMTP server is unreachable");
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
await expect(
|
|
805
|
+
UserService.sendPasswordResetLink({ userId: userId }),
|
|
806
|
+
).rejects.toThrow("SMTP server is unreachable");
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
test("treats a REJECTED mail send as a failure, not a delivery", async () => {
|
|
810
|
+
/*
|
|
811
|
+
* The catch-shaped failure: the notification service was unreachable, so
|
|
812
|
+
* the HTTP client threw. This is the rarer of the two, and it is the only
|
|
813
|
+
* one a bare try/catch would have caught -- see the sibling test below
|
|
814
|
+
* for the one that matters more.
|
|
815
|
+
*/
|
|
816
|
+
resolveUser(buildUser({ id: userId, email: "user@example.com" }));
|
|
817
|
+
|
|
818
|
+
sendMailSpy.mockImplementation(async (): Promise<never> => {
|
|
819
|
+
throw new Error("connect ECONNREFUSED");
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
await expect(
|
|
823
|
+
UserService.sendPasswordResetLink({ userId: userId }),
|
|
824
|
+
).rejects.toThrow("connect ECONNREFUSED");
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
test("treats a RESOLVED HTTPErrorResponse as a failure, not a delivery", async () => {
|
|
828
|
+
/*
|
|
829
|
+
* THE ONE THAT ACTUALLY BITES. MailService.sendMail is an HTTP call, and
|
|
830
|
+
* this codebase's API client only REJECTS when no response came back at
|
|
831
|
+
* all. A response that says no -- 400 "Global SMTP Config not found" on
|
|
832
|
+
* a fresh self-hosted instance, an SMTP auth failure, a rejected
|
|
833
|
+
* recipient -- comes back as a RESOLVED HTTPErrorResponse. And because
|
|
834
|
+
* HTTPErrorResponse extends HTTPResponse, it satisfies sendMail's
|
|
835
|
+
* declared return type, so nothing about it looks wrong to the compiler
|
|
836
|
+
* either.
|
|
837
|
+
*
|
|
838
|
+
* Without an explicit instanceof check, the awaited call returns
|
|
839
|
+
* normally, the rollback never runs, and the operator is told the link
|
|
840
|
+
* was sent while a live 24 hour token sits on a row nobody was emailed
|
|
841
|
+
* about. That is the exact state this method exists to avoid, reached by
|
|
842
|
+
* the most ordinary failure there is.
|
|
843
|
+
*/
|
|
844
|
+
resolveUser(buildUser({ id: userId, email: "user@example.com" }));
|
|
845
|
+
|
|
846
|
+
sendMailSpy.mockImplementation(async (): Promise<any> => {
|
|
847
|
+
return new HTTPErrorResponse(
|
|
848
|
+
400,
|
|
849
|
+
{ message: "Global SMTP Config not found" },
|
|
850
|
+
{},
|
|
851
|
+
);
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
await expect(
|
|
855
|
+
UserService.sendPasswordResetLink({ userId: userId }),
|
|
856
|
+
).rejects.toThrow("Global SMTP Config not found");
|
|
857
|
+
|
|
858
|
+
// ...and the token it had already armed is taken back off the row.
|
|
859
|
+
expect(updateOneByIdSpy).toHaveBeenCalledTimes(2);
|
|
860
|
+
expect(
|
|
861
|
+
updateOneByIdSpy.mock.calls[1]![0].data.resetPasswordToken,
|
|
862
|
+
).toBeFalsy();
|
|
863
|
+
expect(
|
|
864
|
+
updateOneByIdSpy.mock.calls[1]![0].data.resetPasswordExpires,
|
|
865
|
+
).toBeFalsy();
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
test("takes the token back off the row when the mail could not be sent", async () => {
|
|
869
|
+
/*
|
|
870
|
+
* The token is written BEFORE the send, because the link in the email has
|
|
871
|
+
* to be redeemable the moment it lands. That ordering means a failed send
|
|
872
|
+
* would otherwise strand a live 24 hour token on a row nobody was ever
|
|
873
|
+
* told about -- and getAuthenticationStatus would then report "reset link
|
|
874
|
+
* pending" on the same screen as the error saying it could not be sent.
|
|
875
|
+
*
|
|
876
|
+
* So the send is unwound: the second update clears both columns, and the
|
|
877
|
+
* page and the operator end up telling the same story.
|
|
878
|
+
*/
|
|
879
|
+
resolveUser(buildUser({ id: userId, email: "user@example.com" }));
|
|
880
|
+
|
|
881
|
+
sendMailSpy.mockImplementation(async (): Promise<never> => {
|
|
882
|
+
throw new Error("SMTP server is unreachable");
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
await expect(
|
|
886
|
+
UserService.sendPasswordResetLink({ userId: userId }),
|
|
887
|
+
).rejects.toThrow("SMTP server is unreachable");
|
|
888
|
+
|
|
889
|
+
expect(updateOneByIdSpy).toHaveBeenCalledTimes(2);
|
|
890
|
+
|
|
891
|
+
const clearingUpdate: any = updateOneByIdSpy.mock.calls[1]![0];
|
|
892
|
+
|
|
893
|
+
expect(clearingUpdate.id.toString()).toBe(userId.toString());
|
|
894
|
+
expect(clearingUpdate.data.resetPasswordToken).toBeFalsy();
|
|
895
|
+
expect(clearingUpdate.data.resetPasswordExpires).toBeFalsy();
|
|
896
|
+
expect(clearingUpdate.props.isRoot).toBe(true);
|
|
897
|
+
|
|
898
|
+
/*
|
|
899
|
+
* The columns are cleared, not merely overwritten with something else:
|
|
900
|
+
* a non-null token here would still read as a pending link.
|
|
901
|
+
*/
|
|
902
|
+
expect("resetPasswordToken" in clearingUpdate.data).toBe(true);
|
|
903
|
+
expect("resetPasswordExpires" in clearingUpdate.data).toBe(true);
|
|
904
|
+
});
|
|
905
|
+
|
|
906
|
+
test("still reports the mail failure even if the token cannot be cleared", async () => {
|
|
907
|
+
/*
|
|
908
|
+
* The cleanup is best-effort and must never replace the error worth
|
|
909
|
+
* reporting. If it could, an SMTP outage plus a database blip would
|
|
910
|
+
* surface to the operator as a database error about a write they never
|
|
911
|
+
* asked for -- and the orphaned token expires by itself regardless.
|
|
912
|
+
*/
|
|
913
|
+
resolveUser(buildUser({ id: userId, email: "user@example.com" }));
|
|
914
|
+
|
|
915
|
+
sendMailSpy.mockImplementation(async (): Promise<never> => {
|
|
916
|
+
throw new Error("SMTP server is unreachable");
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
updateOneByIdSpy.mockImplementation(
|
|
920
|
+
async (update: any): Promise<void> => {
|
|
921
|
+
if (update.data.resetPasswordToken === null) {
|
|
922
|
+
throw new Error("Database is unavailable");
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
return undefined;
|
|
926
|
+
},
|
|
927
|
+
);
|
|
928
|
+
|
|
929
|
+
await expect(
|
|
930
|
+
UserService.sendPasswordResetLink({ userId: userId }),
|
|
931
|
+
).rejects.toThrow("SMTP server is unreachable");
|
|
932
|
+
});
|
|
933
|
+
|
|
934
|
+
test("does not touch the token again when the mail was sent", async () => {
|
|
935
|
+
/*
|
|
936
|
+
* The mirror of the unwind: on the happy path there is exactly ONE write,
|
|
937
|
+
* the one that armed the link. A second update here would clear the token
|
|
938
|
+
* the user is about to click.
|
|
939
|
+
*/
|
|
940
|
+
resolveUser(buildUser({ id: userId, email: "user@example.com" }));
|
|
941
|
+
|
|
942
|
+
await UserService.sendPasswordResetLink({ userId: userId });
|
|
943
|
+
|
|
944
|
+
expect(updateOneByIdSpy).toHaveBeenCalledTimes(1);
|
|
945
|
+
expect(
|
|
946
|
+
updateOneByIdSpy.mock.calls[0]![0].data.resetPasswordToken,
|
|
947
|
+
).toBeTruthy();
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
test("does not resolve before the mail send settles", async () => {
|
|
951
|
+
/*
|
|
952
|
+
* The failure above can only propagate if the send is awaited. This pins
|
|
953
|
+
* the awaiting directly, so a refactor to fire-and-forget is caught even
|
|
954
|
+
* if it happens to keep rejections visible some other way.
|
|
955
|
+
*/
|
|
956
|
+
resolveUser(buildUser({ id: userId, email: "user@example.com" }));
|
|
957
|
+
|
|
958
|
+
let mailCompleted: boolean = false;
|
|
959
|
+
|
|
960
|
+
sendMailSpy.mockImplementation(async (): Promise<any> => {
|
|
961
|
+
await new Promise<void>((resolve: () => void) => {
|
|
962
|
+
setTimeout(resolve, 10);
|
|
963
|
+
});
|
|
964
|
+
mailCompleted = true;
|
|
965
|
+
return {};
|
|
966
|
+
});
|
|
967
|
+
|
|
968
|
+
await UserService.sendPasswordResetLink({ userId: userId });
|
|
969
|
+
|
|
970
|
+
expect(mailCompleted).toBe(true);
|
|
971
|
+
});
|
|
972
|
+
});
|
|
973
|
+
});
|