@oneuptime/common 12.0.5 → 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 +33 -0
- package/Models/DatabaseModels/DataSource.ts +744 -0
- package/Models/DatabaseModels/Index.ts +3 -0
- package/Models/DatabaseModels/ProjectCallSMSConfig.ts +10 -2
- 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/AIInsightAPI.ts +47 -4
- package/Server/API/AIInvestigationAPI.ts +25 -0
- package/Server/API/DashboardAPI.ts +176 -492
- package/Server/API/DataSourceAPI.ts +268 -0
- package/Server/API/StatusPageAPI.ts +11 -1
- package/Server/API/UserAPI.ts +119 -0
- package/Server/Infrastructure/ClickhouseConfig.ts +59 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786303472848-AddDataSourceTable.ts +39 -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 +6 -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 +34 -0
- package/Server/Services/AIService.ts +17 -2
- package/Server/Services/AlertService.ts +38 -32
- package/Server/Services/AnalyticsDatabaseService.ts +8 -4
- package/Server/Services/DataSourceService.ts +200 -0
- package/Server/Services/HostService.ts +92 -0
- package/Server/Services/IncidentService.ts +45 -0
- package/Server/Services/Index.ts +2 -0
- 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/CodeFix/FixRunBudget.ts +2 -2
- package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +90 -11
- package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +1 -0
- package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +1 -0
- 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/InvestigationTldr.ts +352 -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/MonitorMetricUtil.ts +58 -0
- package/Server/Utils/Monitor/MonitorResource.ts +14 -0
- 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/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/IncidentsSideMenu.test.tsx +447 -0
- package/Tests/App/Dashboard/InvestigationPanel.test.tsx +514 -3
- 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/StatusPageResourceExplorer.test.tsx +1858 -0
- package/Tests/Models/DatabaseModels/DataSourceModelSecrets.test.ts +70 -0
- package/Tests/Server/API/AIInvestigationAPI.test.ts +187 -0
- 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/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/AIServiceDailyBudget.test.ts +4 -0
- package/Tests/Server/Services/AlertMetricResourceAttributes.test.ts +352 -0
- package/Tests/Server/Services/DataSourceServiceSettings.test.ts +244 -0
- 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/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/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/FixRunBudget.test.ts +2 -2
- 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/NewExceptionDetector.test.ts +361 -10
- package/Tests/Server/Utils/AI/InvestigationSettlement.test.ts +292 -0
- package/Tests/Server/Utils/AI/InvestigationTldr.test.ts +623 -0
- package/Tests/Server/Utils/AI/InvestigationTldrWiring.test.ts +98 -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/MonitorMetricResourceAttributes.test.ts +336 -0
- package/Tests/Server/Utils/OfflineAssetHygiene.test.ts +521 -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/DataSourceComponentTypes.test.ts +618 -0
- package/Tests/Types/DateClickhouseFormat.test.ts +192 -0
- package/Tests/Types/Password.test.ts +363 -0
- package/Tests/UI/Components/ConfirmModal.test.tsx +7 -0
- package/Tests/UI/Components/ErrorBoundary.test.tsx +53 -0
- package/Tests/UI/Components/Modal.test.tsx +281 -1
- package/Tests/UI/Components/ObjectIdFilterPipeline.test.tsx +396 -0
- package/Tests/UI/Components/StatusPage/ResourceGroupNavigator.test.tsx +1147 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +116 -1
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +2 -0
- package/Tests/Utils/Analytics.test.ts +23 -0
- package/Tests/Utils/Dashboard/Components/DashboardDataSourceComponents.test.ts +640 -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/StatusPage/GroupHierarchyView.test.ts +1452 -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/Types/AI/AIInsightEvidence.ts +6 -0
- 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/Password.ts +64 -0
- package/Types/Permission.ts +46 -0
- package/Types/UserAuthenticationStatus.ts +34 -0
- package/UI/Components/Button/Button.tsx +1 -1
- package/UI/Components/ErrorBoundary.tsx +459 -108
- 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/MoreMenu/MoreMenu.tsx +19 -4
- package/UI/Components/MoreMenu/MoreMenuItem.tsx +6 -1
- 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/ModelAPI/ModelAPI.ts +1 -0
- package/UI/Utils/TestDataSource.ts +61 -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/DataSource/DataSourceQueryText.ts +94 -0
- package/Utils/DataSource/DataSourceValueReducer.ts +156 -0
- package/Utils/Metrics/MetricResourceAttributeUtil.ts +380 -0
- package/Utils/NetworkDiscovery/ScanTargetUtil.ts +8 -6
- package/Utils/StatusPage/GroupHierarchyView.ts +931 -0
- package/Utils/StatusPage/ResourceExplorer.ts +853 -0
- package/build/dist/Models/DatabaseModels/AIRun.js +34 -0
- package/build/dist/Models/DatabaseModels/AIRun.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/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.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/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/AIInsightAPI.js +34 -4
- package/build/dist/Server/API/AIInsightAPI.js.map +1 -1
- package/build/dist/Server/API/AIInvestigationAPI.js +23 -0
- package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
- package/build/dist/Server/API/DashboardAPI.js +119 -433
- 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 +11 -1
- 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/Infrastructure/ClickhouseConfig.js +49 -0
- package/build/dist/Server/Infrastructure/ClickhouseConfig.js.map +1 -1
- 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/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 +6 -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 +34 -0
- package/build/dist/Server/Services/AIRunService.js.map +1 -1
- package/build/dist/Server/Services/AIService.js +16 -2
- package/build/dist/Server/Services/AIService.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 +8 -4
- 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/HostService.js +74 -0
- package/build/dist/Server/Services/HostService.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/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/CodeFix/FixRunBudget.js +2 -2
- package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +68 -8
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +1 -0
- package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +1 -0
- 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/InvestigationTldr.js +303 -0
- package/build/dist/Server/Utils/AI/SRE/InvestigationTldr.js.map +1 -0
- 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/MonitorMetricUtil.js +34 -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/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/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/Password.js +56 -0
- package/build/dist/Types/Password.js.map +1 -0
- package/build/dist/Types/Permission.js +42 -0
- 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/ErrorBoundary.js +388 -72
- package/build/dist/UI/Components/ErrorBoundary.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/MoreMenu/MoreMenu.js +9 -6
- 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/ModelAPI/ModelAPI.js +3 -8
- package/build/dist/UI/Utils/ModelAPI/ModelAPI.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/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/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/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/ResourceExplorer.js +519 -0
- package/build/dist/Utils/StatusPage/ResourceExplorer.js.map +1 -0
- package/jest.config.json +1 -0
- package/package.json +5 -1
- package/tsconfig.json +12 -10
|
@@ -0,0 +1,641 @@
|
|
|
1
|
+
import UserAPI from "../../../Server/API/UserAPI";
|
|
2
|
+
import MasterAdminAuthorization from "../../../Server/Middleware/MasterAdminAuthorization";
|
|
3
|
+
import UserService from "../../../Server/Services/UserService";
|
|
4
|
+
import {
|
|
5
|
+
ExpressRequest,
|
|
6
|
+
ExpressResponse,
|
|
7
|
+
NextFunction,
|
|
8
|
+
} from "../../../Server/Utils/Express";
|
|
9
|
+
import Response from "../../../Server/Utils/Response";
|
|
10
|
+
import { mockRouter } from "./Helpers";
|
|
11
|
+
import Dictionary from "../../../Types/Dictionary";
|
|
12
|
+
import Email from "../../../Types/Email";
|
|
13
|
+
import BadDataException from "../../../Types/Exception/BadDataException";
|
|
14
|
+
import NotFoundException from "../../../Types/Exception/NotFoundException";
|
|
15
|
+
import { JSONObject } from "../../../Types/JSON";
|
|
16
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
17
|
+
import UserAuthenticationStatus from "../../../Types/UserAuthenticationStatus";
|
|
18
|
+
import {
|
|
19
|
+
afterEach,
|
|
20
|
+
beforeAll,
|
|
21
|
+
beforeEach,
|
|
22
|
+
describe,
|
|
23
|
+
expect,
|
|
24
|
+
test,
|
|
25
|
+
} from "@jest/globals";
|
|
26
|
+
|
|
27
|
+
/*
|
|
28
|
+
* The three master-admin endpoints behind the Admin Dashboard's
|
|
29
|
+
* User > Authentication page.
|
|
30
|
+
*
|
|
31
|
+
* GET /user/:userId/authentication-status exists so that "does this account
|
|
32
|
+
* have a password at all?" can be answered without the password column ever
|
|
33
|
+
* leaving the server. A master admin bypasses column read permissions, so the
|
|
34
|
+
* obvious alternative -- select `password` through the CRUD API and check it
|
|
35
|
+
* in the browser -- would put every user's scrypt digest in a page's network
|
|
36
|
+
* tab. The route therefore ships four derived booleans, and marks them
|
|
37
|
+
* uncacheable: whether somebody can sign in with a password is not a fact a
|
|
38
|
+
* shared proxy may keep, and it changes the instant either action below runs.
|
|
39
|
+
*
|
|
40
|
+
* POST /user/:userId/set-password is a deliberately separate route rather than
|
|
41
|
+
* a PUT to /user/:id carrying a password, so that the gate is a middleware
|
|
42
|
+
* anybody can grep for. The body's `password` is TYPE-CHECKED rather than
|
|
43
|
+
* cast, and that check is load-bearing in a way no other test here would
|
|
44
|
+
* notice: an `as string` on a JSON body is a lie the moment a caller posts an
|
|
45
|
+
* object or a number, and the value flows straight into the hasher.
|
|
46
|
+
*
|
|
47
|
+
* POST /user/:userId/send-password-reset-link is the safer twin -- it never
|
|
48
|
+
* puts the operator in possession of the user's credentials -- and echoes back
|
|
49
|
+
* the address it mailed so the page can say where the link went.
|
|
50
|
+
*
|
|
51
|
+
* All three are guarded by the master-admin middleware. That is THE assertion
|
|
52
|
+
* in this file: without it, any signed-in user could set any other user's
|
|
53
|
+
* password and take over their account. Nothing else in these tests would
|
|
54
|
+
* notice the middleware going missing, so each route is checked for it
|
|
55
|
+
* directly.
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
jest.mock("../../../Server/Utils/Express", () => {
|
|
59
|
+
return {
|
|
60
|
+
getRouter: () => {
|
|
61
|
+
return mockRouter;
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
jest.mock("../../../Server/Utils/Response", () => {
|
|
67
|
+
return {
|
|
68
|
+
sendEntityArrayResponse: jest.fn(),
|
|
69
|
+
sendJsonArrayResponse: jest.fn(),
|
|
70
|
+
sendJsonObjectResponse: jest.fn(),
|
|
71
|
+
sendEmptySuccessResponse: jest.fn(),
|
|
72
|
+
sendEntityResponse: jest.fn(),
|
|
73
|
+
sendErrorResponse: jest.fn(),
|
|
74
|
+
sendFileResponse: jest.fn(),
|
|
75
|
+
sendFileByPath: jest.fn(),
|
|
76
|
+
setNoCacheHeaders: jest.fn(),
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const STATUS_ROUTE: string = "/user/:userId/authentication-status";
|
|
81
|
+
const SET_PASSWORD_ROUTE: string = "/user/:userId/set-password";
|
|
82
|
+
const RESET_LINK_ROUTE: string = "/user/:userId/send-password-reset-link";
|
|
83
|
+
|
|
84
|
+
const USER_ID: string = "00000000-0000-4000-8000-000000000001";
|
|
85
|
+
|
|
86
|
+
const AUTHENTICATION_STATUS: UserAuthenticationStatus = {
|
|
87
|
+
hasPassword: true,
|
|
88
|
+
isEmailVerified: false,
|
|
89
|
+
isTwoFactorAuthEnabled: true,
|
|
90
|
+
hasPendingPasswordResetLink: false,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
type RouteCallResult = {
|
|
94
|
+
thrownToNext: unknown;
|
|
95
|
+
nextCallCount: number;
|
|
96
|
+
res: ExpressResponse;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
async function callRoute(data: {
|
|
100
|
+
method: string;
|
|
101
|
+
route: string;
|
|
102
|
+
params?: Dictionary<string> | undefined;
|
|
103
|
+
query?: Dictionary<string> | undefined;
|
|
104
|
+
body?: Dictionary<unknown> | undefined;
|
|
105
|
+
}): Promise<RouteCallResult> {
|
|
106
|
+
const req: ExpressRequest = {
|
|
107
|
+
params: data.params || {},
|
|
108
|
+
query: data.query || {},
|
|
109
|
+
body: data.body || {},
|
|
110
|
+
headers: {},
|
|
111
|
+
} as unknown as ExpressRequest;
|
|
112
|
+
|
|
113
|
+
const res: ExpressResponse = {
|
|
114
|
+
send: jest.fn(),
|
|
115
|
+
json: jest.fn(),
|
|
116
|
+
setHeader: jest.fn(),
|
|
117
|
+
status: jest.fn().mockReturnThis(),
|
|
118
|
+
} as unknown as ExpressResponse;
|
|
119
|
+
|
|
120
|
+
const next: jest.Mock = jest.fn();
|
|
121
|
+
|
|
122
|
+
await mockRouter
|
|
123
|
+
.match(data.method, data.route)
|
|
124
|
+
.handlerFunction(req, res, next as unknown as NextFunction);
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
thrownToNext: next.mock.calls[0] ? next.mock.calls[0][0] : undefined,
|
|
128
|
+
nextCallCount: next.mock.calls.length,
|
|
129
|
+
res: res,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function asMock(fn: unknown): jest.Mock {
|
|
134
|
+
return fn as unknown as jest.Mock;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function sentJsonObject(): JSONObject {
|
|
138
|
+
const call: Array<unknown> | undefined = asMock(
|
|
139
|
+
Response.sendJsonObjectResponse,
|
|
140
|
+
).mock.calls[0];
|
|
141
|
+
|
|
142
|
+
return (call?.[2] as JSONObject) || {};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
describe("GET /user/:userId/authentication-status", () => {
|
|
146
|
+
let statusSpy: jest.SpyInstance;
|
|
147
|
+
|
|
148
|
+
beforeAll(() => {
|
|
149
|
+
mockRouter.routes.length = 0;
|
|
150
|
+
new UserAPI();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
beforeEach(() => {
|
|
154
|
+
jest.clearAllMocks();
|
|
155
|
+
|
|
156
|
+
statusSpy = jest
|
|
157
|
+
.spyOn(UserService, "getAuthenticationStatus")
|
|
158
|
+
.mockResolvedValue(AUTHENTICATION_STATUS);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
afterEach(() => {
|
|
162
|
+
jest.restoreAllMocks();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("the route is registered", () => {
|
|
166
|
+
/*
|
|
167
|
+
* A typo in the path is not a compile error and not a test failure
|
|
168
|
+
* anywhere else — it is a 404 that the page surfaces as an unexplained
|
|
169
|
+
* error the first time an operator opens it.
|
|
170
|
+
*/
|
|
171
|
+
expect(() => {
|
|
172
|
+
return mockRouter.match("GET", STATUS_ROUTE);
|
|
173
|
+
}).not.toThrow();
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("only a master admin can reach it", () => {
|
|
177
|
+
expect(mockRouter.match("GET", STATUS_ROUTE).middleware).toBe(
|
|
178
|
+
MasterAdminAuthorization.isAuthorizedMasterAdminMiddleware,
|
|
179
|
+
);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("reads the status of the user in the path", async () => {
|
|
183
|
+
await callRoute({
|
|
184
|
+
method: "GET",
|
|
185
|
+
route: STATUS_ROUTE,
|
|
186
|
+
params: { userId: USER_ID },
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
expect(statusSpy).toHaveBeenCalledTimes(1);
|
|
190
|
+
expect((statusSpy.mock.calls[0]![0] as ObjectID).toString()).toBe(USER_ID);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("sends exactly what the service reported", async () => {
|
|
194
|
+
/*
|
|
195
|
+
* The page renders these four booleans directly. A field dropped or
|
|
196
|
+
* renamed on the way out reads as `undefined`, which the UI shows as "no
|
|
197
|
+
* password" for an account that has one.
|
|
198
|
+
*/
|
|
199
|
+
await callRoute({
|
|
200
|
+
method: "GET",
|
|
201
|
+
route: STATUS_ROUTE,
|
|
202
|
+
params: { userId: USER_ID },
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
expect(sentJsonObject()).toEqual({
|
|
206
|
+
hasPassword: true,
|
|
207
|
+
isEmailVerified: false,
|
|
208
|
+
isTwoFactorAuthEnabled: true,
|
|
209
|
+
hasPendingPasswordResetLink: false,
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test("tells caches not to keep the answer", async () => {
|
|
214
|
+
/*
|
|
215
|
+
* Whether an account can sign in with a password is a fact about one named
|
|
216
|
+
* user, served over a session-authenticated GET. Without no-store a shared
|
|
217
|
+
* proxy is entitled to hand it to the next caller — and it is stale the
|
|
218
|
+
* moment either action on the page is used.
|
|
219
|
+
*/
|
|
220
|
+
const result: RouteCallResult = await callRoute({
|
|
221
|
+
method: "GET",
|
|
222
|
+
route: STATUS_ROUTE,
|
|
223
|
+
params: { userId: USER_ID },
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
expect(Response.setNoCacheHeaders).toHaveBeenCalledTimes(1);
|
|
227
|
+
expect(Response.setNoCacheHeaders).toHaveBeenCalledWith(result.res);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test("sets the no-cache headers before the body goes out", async () => {
|
|
231
|
+
/*
|
|
232
|
+
* Headers written after the response has been sent are ignored, so the
|
|
233
|
+
* ORDER is the whole protection — not merely that both calls happened.
|
|
234
|
+
*/
|
|
235
|
+
await callRoute({
|
|
236
|
+
method: "GET",
|
|
237
|
+
route: STATUS_ROUTE,
|
|
238
|
+
params: { userId: USER_ID },
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
const noCacheOrder: number = asMock(Response.setNoCacheHeaders).mock
|
|
242
|
+
.invocationCallOrder[0]!;
|
|
243
|
+
const sendOrder: number = asMock(Response.sendJsonObjectResponse).mock
|
|
244
|
+
.invocationCallOrder[0]!;
|
|
245
|
+
|
|
246
|
+
expect(noCacheOrder).toBeLessThan(sendOrder);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("rejects a missing user id without reading anything", async () => {
|
|
250
|
+
const result: RouteCallResult = await callRoute({
|
|
251
|
+
method: "GET",
|
|
252
|
+
route: STATUS_ROUTE,
|
|
253
|
+
params: {},
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
expect(statusSpy).not.toHaveBeenCalled();
|
|
257
|
+
expect(result.nextCallCount).toBe(1);
|
|
258
|
+
expect(result.thrownToNext).toBeInstanceOf(BadDataException);
|
|
259
|
+
expect(Response.sendJsonObjectResponse).not.toHaveBeenCalled();
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test("rejects a user id that is not a uuid without reading anything", async () => {
|
|
263
|
+
const result: RouteCallResult = await callRoute({
|
|
264
|
+
method: "GET",
|
|
265
|
+
route: STATUS_ROUTE,
|
|
266
|
+
params: { userId: '\'; DROP TABLE "User"; --' },
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
expect(statusSpy).not.toHaveBeenCalled();
|
|
270
|
+
expect(result.nextCallCount).toBe(1);
|
|
271
|
+
expect(Response.sendJsonObjectResponse).not.toHaveBeenCalled();
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test("passes a read failure on rather than reporting a status", async () => {
|
|
275
|
+
/*
|
|
276
|
+
* "No such user" must reach the caller as a failure. Swallowed, the page
|
|
277
|
+
* would render the default booleans and tell an operator that a user who
|
|
278
|
+
* does not exist has no password.
|
|
279
|
+
*/
|
|
280
|
+
const readError: NotFoundException = new NotFoundException(
|
|
281
|
+
"User not found.",
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
statusSpy.mockRejectedValue(readError);
|
|
285
|
+
|
|
286
|
+
const result: RouteCallResult = await callRoute({
|
|
287
|
+
method: "GET",
|
|
288
|
+
route: STATUS_ROUTE,
|
|
289
|
+
params: { userId: USER_ID },
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
expect(result.thrownToNext).toBe(readError);
|
|
293
|
+
expect(result.nextCallCount).toBe(1);
|
|
294
|
+
expect(Response.sendJsonObjectResponse).not.toHaveBeenCalled();
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
describe("POST /user/:userId/set-password", () => {
|
|
299
|
+
let setPasswordSpy: jest.SpyInstance;
|
|
300
|
+
|
|
301
|
+
beforeAll(() => {
|
|
302
|
+
mockRouter.routes.length = 0;
|
|
303
|
+
new UserAPI();
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
beforeEach(() => {
|
|
307
|
+
jest.clearAllMocks();
|
|
308
|
+
|
|
309
|
+
setPasswordSpy = jest
|
|
310
|
+
.spyOn(UserService, "setPassword")
|
|
311
|
+
.mockResolvedValue(undefined);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
afterEach(() => {
|
|
315
|
+
jest.restoreAllMocks();
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
const expectRejected: (body: Dictionary<unknown>) => Promise<void> = async (
|
|
319
|
+
body: Dictionary<unknown>,
|
|
320
|
+
): Promise<void> => {
|
|
321
|
+
const result: RouteCallResult = await callRoute({
|
|
322
|
+
method: "POST",
|
|
323
|
+
route: SET_PASSWORD_ROUTE,
|
|
324
|
+
params: { userId: USER_ID },
|
|
325
|
+
body: body,
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
expect(setPasswordSpy).not.toHaveBeenCalled();
|
|
329
|
+
expect(result.nextCallCount).toBe(1);
|
|
330
|
+
expect(result.thrownToNext).toBeInstanceOf(BadDataException);
|
|
331
|
+
expect(Response.sendEmptySuccessResponse).not.toHaveBeenCalled();
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
test("the route is registered", () => {
|
|
335
|
+
expect(() => {
|
|
336
|
+
return mockRouter.match("POST", SET_PASSWORD_ROUTE);
|
|
337
|
+
}).not.toThrow();
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
test("only a master admin can reach it", () => {
|
|
341
|
+
/*
|
|
342
|
+
* The single most important line in this file. This route rewrites another
|
|
343
|
+
* person's credentials; with the middleware gone, every signed-in user on
|
|
344
|
+
* the instance could take over every account on it.
|
|
345
|
+
*/
|
|
346
|
+
expect(mockRouter.match("POST", SET_PASSWORD_ROUTE).middleware).toBe(
|
|
347
|
+
MasterAdminAuthorization.isAuthorizedMasterAdminMiddleware,
|
|
348
|
+
);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
test("sets the password of the user in the path", async () => {
|
|
352
|
+
await callRoute({
|
|
353
|
+
method: "POST",
|
|
354
|
+
route: SET_PASSWORD_ROUTE,
|
|
355
|
+
params: { userId: USER_ID },
|
|
356
|
+
body: { password: "correct-horse" },
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
expect(setPasswordSpy).toHaveBeenCalledTimes(1);
|
|
360
|
+
|
|
361
|
+
const call: { userId: ObjectID; password: string } = setPasswordSpy.mock
|
|
362
|
+
.calls[0]![0] as { userId: ObjectID; password: string };
|
|
363
|
+
|
|
364
|
+
expect(call.userId.toString()).toBe(USER_ID);
|
|
365
|
+
expect(call.password).toBe("correct-horse");
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
test("forwards the password byte for byte, spaces and all", async () => {
|
|
369
|
+
/*
|
|
370
|
+
* A password is whatever the person typed. Trimming, normalising or
|
|
371
|
+
* re-encoding it here would store something other than what was entered,
|
|
372
|
+
* and the very next sign-in would fail with no clue as to why.
|
|
373
|
+
*/
|
|
374
|
+
const password: string = " pässwörd ✅ 🔐 ";
|
|
375
|
+
|
|
376
|
+
await callRoute({
|
|
377
|
+
method: "POST",
|
|
378
|
+
route: SET_PASSWORD_ROUTE,
|
|
379
|
+
params: { userId: USER_ID },
|
|
380
|
+
body: { password: password },
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
expect(
|
|
384
|
+
(setPasswordSpy.mock.calls[0]![0] as { password: string }).password,
|
|
385
|
+
).toBe(password);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
test("leaves the password policy to the service rather than pre-judging it", async () => {
|
|
389
|
+
/*
|
|
390
|
+
* The route's only job is the type check; length and blankness rules live
|
|
391
|
+
* in one place (Types/Password) behind the service, so the browser form
|
|
392
|
+
* and the endpoint cannot drift apart. A short string is a STRING, so it
|
|
393
|
+
* must reach the service and be refused there — a duplicate rule here
|
|
394
|
+
* would be a second copy free to disagree.
|
|
395
|
+
*/
|
|
396
|
+
await callRoute({
|
|
397
|
+
method: "POST",
|
|
398
|
+
route: SET_PASSWORD_ROUTE,
|
|
399
|
+
params: { userId: USER_ID },
|
|
400
|
+
body: { password: "abc" },
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
expect(setPasswordSpy).toHaveBeenCalledTimes(1);
|
|
404
|
+
expect(
|
|
405
|
+
(setPasswordSpy.mock.calls[0]![0] as { password: string }).password,
|
|
406
|
+
).toBe("abc");
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
test("reports success with an empty body, never echoing the password", async () => {
|
|
410
|
+
await callRoute({
|
|
411
|
+
method: "POST",
|
|
412
|
+
route: SET_PASSWORD_ROUTE,
|
|
413
|
+
params: { userId: USER_ID },
|
|
414
|
+
body: { password: "correct-horse" },
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
expect(Response.sendEmptySuccessResponse).toHaveBeenCalledTimes(1);
|
|
418
|
+
expect(Response.sendJsonObjectResponse).not.toHaveBeenCalled();
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
test("rejects a missing password", async () => {
|
|
422
|
+
await expectRejected({});
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
test("rejects a null password", async () => {
|
|
426
|
+
await expectRejected({ password: null });
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
test("rejects a numeric password", async () => {
|
|
430
|
+
/*
|
|
431
|
+
* JSON numbers arrive as numbers. `123456` would satisfy a length check
|
|
432
|
+
* written against `String(password)` while being nothing a person could
|
|
433
|
+
* ever type at a sign-in form.
|
|
434
|
+
*/
|
|
435
|
+
await expectRejected({ password: 123456 });
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
test("rejects a boolean password", async () => {
|
|
439
|
+
await expectRejected({ password: true });
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
test("rejects an object password", async () => {
|
|
443
|
+
/*
|
|
444
|
+
* THE case this type check exists for. Had the handler written
|
|
445
|
+
* `req.body["password"] as string`, TypeScript would have believed it and
|
|
446
|
+
* a JSON object would have sailed into the hasher: HashedString would then
|
|
447
|
+
* hash whatever `String(value)` produces — "[object Object]" for a plain
|
|
448
|
+
* object, or, worse, an attacker-chosen value where the object carries its
|
|
449
|
+
* own `toString`. Every account set that way would share one password.
|
|
450
|
+
* A cast cannot catch this; only a runtime `typeof` can.
|
|
451
|
+
*/
|
|
452
|
+
await expectRejected({ password: { toString: "[object Object]" } });
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
test("rejects a nested object password", async () => {
|
|
456
|
+
await expectRejected({ password: { value: "correct-horse" } });
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
test("rejects an array password", async () => {
|
|
460
|
+
// Arrays are objects, and `String(["hunter2"])` is the innocuous "hunter2".
|
|
461
|
+
await expectRejected({ password: ["correct-horse"] });
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
test("rejects a missing user id without setting anything", async () => {
|
|
465
|
+
const result: RouteCallResult = await callRoute({
|
|
466
|
+
method: "POST",
|
|
467
|
+
route: SET_PASSWORD_ROUTE,
|
|
468
|
+
params: {},
|
|
469
|
+
body: { password: "correct-horse" },
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
expect(setPasswordSpy).not.toHaveBeenCalled();
|
|
473
|
+
expect(result.nextCallCount).toBe(1);
|
|
474
|
+
expect(Response.sendEmptySuccessResponse).not.toHaveBeenCalled();
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
test("rejects a user id that is not a uuid without setting anything", async () => {
|
|
478
|
+
const result: RouteCallResult = await callRoute({
|
|
479
|
+
method: "POST",
|
|
480
|
+
route: SET_PASSWORD_ROUTE,
|
|
481
|
+
params: { userId: "not-a-uuid" },
|
|
482
|
+
body: { password: "correct-horse" },
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
expect(setPasswordSpy).not.toHaveBeenCalled();
|
|
486
|
+
expect(result.nextCallCount).toBe(1);
|
|
487
|
+
expect(Response.sendEmptySuccessResponse).not.toHaveBeenCalled();
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
test("passes a write failure on rather than reporting success", async () => {
|
|
491
|
+
/*
|
|
492
|
+
* The policy rejection and "user not found" both arrive this way. Reported
|
|
493
|
+
* as success, an operator would hand somebody a password that was never
|
|
494
|
+
* stored.
|
|
495
|
+
*/
|
|
496
|
+
const writeError: BadDataException = new BadDataException(
|
|
497
|
+
"Password cannot be less than 6 characters.",
|
|
498
|
+
);
|
|
499
|
+
|
|
500
|
+
setPasswordSpy.mockRejectedValue(writeError);
|
|
501
|
+
|
|
502
|
+
const result: RouteCallResult = await callRoute({
|
|
503
|
+
method: "POST",
|
|
504
|
+
route: SET_PASSWORD_ROUTE,
|
|
505
|
+
params: { userId: USER_ID },
|
|
506
|
+
body: { password: "abc" },
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
expect(result.thrownToNext).toBe(writeError);
|
|
510
|
+
expect(result.nextCallCount).toBe(1);
|
|
511
|
+
expect(Response.sendEmptySuccessResponse).not.toHaveBeenCalled();
|
|
512
|
+
});
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
describe("POST /user/:userId/send-password-reset-link", () => {
|
|
516
|
+
let sendLinkSpy: jest.SpyInstance;
|
|
517
|
+
|
|
518
|
+
beforeAll(() => {
|
|
519
|
+
mockRouter.routes.length = 0;
|
|
520
|
+
new UserAPI();
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
beforeEach(() => {
|
|
524
|
+
jest.clearAllMocks();
|
|
525
|
+
|
|
526
|
+
sendLinkSpy = jest
|
|
527
|
+
.spyOn(UserService, "sendPasswordResetLink")
|
|
528
|
+
.mockResolvedValue(new Email("locked.out@example.com"));
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
afterEach(() => {
|
|
532
|
+
jest.restoreAllMocks();
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
test("the route is registered", () => {
|
|
536
|
+
expect(() => {
|
|
537
|
+
return mockRouter.match("POST", RESET_LINK_ROUTE);
|
|
538
|
+
}).not.toThrow();
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
test("only a master admin can reach it", () => {
|
|
542
|
+
/*
|
|
543
|
+
* Mailing a reset link is a credential-changing action too: it is the
|
|
544
|
+
* password takeover with one extra step, taken by whoever controls the
|
|
545
|
+
* mailbox.
|
|
546
|
+
*/
|
|
547
|
+
expect(mockRouter.match("POST", RESET_LINK_ROUTE).middleware).toBe(
|
|
548
|
+
MasterAdminAuthorization.isAuthorizedMasterAdminMiddleware,
|
|
549
|
+
);
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
test("sends the link to the user in the path", async () => {
|
|
553
|
+
await callRoute({
|
|
554
|
+
method: "POST",
|
|
555
|
+
route: RESET_LINK_ROUTE,
|
|
556
|
+
params: { userId: USER_ID },
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
expect(sendLinkSpy).toHaveBeenCalledTimes(1);
|
|
560
|
+
expect(
|
|
561
|
+
(sendLinkSpy.mock.calls[0]![0] as { userId: ObjectID }).userId.toString(),
|
|
562
|
+
).toBe(USER_ID);
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
test("reports the address the link actually went to", async () => {
|
|
566
|
+
await callRoute({
|
|
567
|
+
method: "POST",
|
|
568
|
+
route: RESET_LINK_ROUTE,
|
|
569
|
+
params: { userId: USER_ID },
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
expect(sentJsonObject()).toEqual({ email: "locked.out@example.com" });
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
test("reports the address as a plain string, not a serialized Email", async () => {
|
|
576
|
+
/*
|
|
577
|
+
* The service returns an Email, whose toJSON is
|
|
578
|
+
* `{_type: "Email", value: "..."}`. Sending the object itself would still
|
|
579
|
+
* be a 200 with an `email` key, so nothing fails loudly — the page just
|
|
580
|
+
* renders "[object Object]" in the sentence telling the operator where the
|
|
581
|
+
* link was sent, which is the one fact they came for.
|
|
582
|
+
*/
|
|
583
|
+
await callRoute({
|
|
584
|
+
method: "POST",
|
|
585
|
+
route: RESET_LINK_ROUTE,
|
|
586
|
+
params: { userId: USER_ID },
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
const email: unknown = sentJsonObject()["email"];
|
|
590
|
+
|
|
591
|
+
expect(typeof email).toBe("string");
|
|
592
|
+
expect(email).toBe("locked.out@example.com");
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
test("rejects a missing user id without sending anything", async () => {
|
|
596
|
+
const result: RouteCallResult = await callRoute({
|
|
597
|
+
method: "POST",
|
|
598
|
+
route: RESET_LINK_ROUTE,
|
|
599
|
+
params: {},
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
expect(sendLinkSpy).not.toHaveBeenCalled();
|
|
603
|
+
expect(result.nextCallCount).toBe(1);
|
|
604
|
+
expect(result.thrownToNext).toBeInstanceOf(BadDataException);
|
|
605
|
+
expect(Response.sendJsonObjectResponse).not.toHaveBeenCalled();
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
test("rejects a user id that is not a uuid without sending anything", async () => {
|
|
609
|
+
const result: RouteCallResult = await callRoute({
|
|
610
|
+
method: "POST",
|
|
611
|
+
route: RESET_LINK_ROUTE,
|
|
612
|
+
params: { userId: '\'; DROP TABLE "User"; --' },
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
expect(sendLinkSpy).not.toHaveBeenCalled();
|
|
616
|
+
expect(result.nextCallCount).toBe(1);
|
|
617
|
+
expect(Response.sendJsonObjectResponse).not.toHaveBeenCalled();
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
test("passes a mail failure on rather than reporting the link was sent", async () => {
|
|
621
|
+
/*
|
|
622
|
+
* The service awaits the send precisely so that an instance which cannot
|
|
623
|
+
* send mail says so. Swallowing it here would put the operator back where
|
|
624
|
+
* they started: told the link is on its way, waiting for one that will
|
|
625
|
+
* never arrive.
|
|
626
|
+
*/
|
|
627
|
+
const mailError: Error = new Error("SMTP server is not configured.");
|
|
628
|
+
|
|
629
|
+
sendLinkSpy.mockRejectedValue(mailError);
|
|
630
|
+
|
|
631
|
+
const result: RouteCallResult = await callRoute({
|
|
632
|
+
method: "POST",
|
|
633
|
+
route: RESET_LINK_ROUTE,
|
|
634
|
+
params: { userId: USER_ID },
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
expect(result.thrownToNext).toBe(mailError);
|
|
638
|
+
expect(result.nextCallCount).toBe(1);
|
|
639
|
+
expect(Response.sendJsonObjectResponse).not.toHaveBeenCalled();
|
|
640
|
+
});
|
|
641
|
+
});
|