@oneuptime/common 12.0.15 → 12.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/AnalyticsModels/Index.ts +10 -0
- package/Models/AnalyticsModels/LogCountBaseline.ts +174 -0
- package/Models/AnalyticsModels/SecurityEvent.ts +794 -0
- package/Models/AnalyticsModels/Span.ts +29 -1
- package/Models/AnalyticsModels/SpanCountBaseline.ts +198 -0
- package/Models/DatabaseModels/Alert.ts +42 -0
- package/Models/DatabaseModels/AlertMeasurement.ts +1114 -0
- package/Models/DatabaseModels/AlertMeasurementValue.ts +501 -0
- package/Models/DatabaseModels/AlertStateTimeline.ts +10 -2
- package/Models/DatabaseModels/BillingPaymentMethod.ts +8 -10
- package/Models/DatabaseModels/DetectionRule.ts +487 -0
- package/Models/DatabaseModels/GoogleSecOpsConnection.ts +382 -0
- package/Models/DatabaseModels/Incident.ts +42 -0
- package/Models/DatabaseModels/IncidentMeasurement.ts +1114 -0
- package/Models/DatabaseModels/IncidentMeasurementValue.ts +501 -0
- package/Models/DatabaseModels/IncidentStateTimeline.ts +10 -2
- package/Models/DatabaseModels/IncomingCallPolicyLabelRule.ts +36 -0
- package/Models/DatabaseModels/IncomingCallPolicyOwnerRule.ts +42 -0
- package/Models/DatabaseModels/Index.ts +20 -0
- package/Models/DatabaseModels/Label.ts +11 -0
- package/Models/DatabaseModels/LlmCostBudget.ts +655 -0
- package/Models/DatabaseModels/LlmModelPrice.ts +481 -0
- package/Models/DatabaseModels/LogSavedView.ts +87 -0
- package/Models/DatabaseModels/MarketingConversion.ts +210 -8
- package/Models/DatabaseModels/MetricSavedView.ts +59 -0
- package/Models/DatabaseModels/OnCallDutyPolicyLabelRule.ts +36 -0
- package/Models/DatabaseModels/OnCallDutyPolicyOwnerRule.ts +42 -0
- package/Models/DatabaseModels/OnCallDutyPolicyScheduleLabelRule.ts +36 -0
- package/Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerRule.ts +42 -0
- package/Models/DatabaseModels/Probe.ts +21 -0
- package/Models/DatabaseModels/RunbookLabelRule.ts +36 -0
- package/Models/DatabaseModels/RunbookOwnerRule.ts +42 -0
- package/Models/DatabaseModels/ScheduledMaintenanceMeasurement.ts +1104 -0
- package/Models/DatabaseModels/ScheduledMaintenanceMeasurementValue.ts +491 -0
- package/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.ts +10 -2
- package/Models/DatabaseModels/StatusPageLabelRule.ts +36 -0
- package/Models/DatabaseModels/StatusPageOwnerRule.ts +42 -0
- package/Models/DatabaseModels/TableView.ts +15 -0
- package/Models/DatabaseModels/Team.ts +13 -0
- package/Models/DatabaseModels/TeamMember.ts +8 -0
- package/Models/DatabaseModels/TraceSavedView.ts +52 -0
- package/Models/DatabaseModels/WorkflowLabelRule.ts +36 -0
- package/Models/DatabaseModels/WorkflowOwnerRule.ts +42 -0
- package/Server/API/TelemetryAPI.ts +252 -6
- package/Server/API/UserAPI.ts +88 -0
- package/Server/EnvironmentConfig.ts +36 -1
- package/Server/Infrastructure/Postgres/SchemaMigrations/1788000000000-AddDetectionRuleAndGoogleSecOpsConnection.ts +88 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1788100000000-AddMeasurements.ts +457 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1788200000000-AddLlmCostBudget.ts +97 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1788300000000-AddLlmModelPrice.ts +45 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1788400000000-AddMarketingConversionAttribution.ts +105 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1788500000000-RemoveLlmCostBudgetAlertColumns.ts +77 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +12 -0
- package/Server/Middleware/IdentityRateLimit.ts +672 -0
- package/Server/Services/AccessTokenService.ts +15 -2
- package/Server/Services/AlertMeasurementService.ts +302 -0
- package/Server/Services/AlertMeasurementValueService.ts +508 -0
- package/Server/Services/AlertService.ts +50 -0
- package/Server/Services/AlertStateTimelineService.ts +54 -0
- package/Server/Services/DetectionRuleService.ts +74 -0
- package/Server/Services/GoogleSecOpsConnectionService.ts +89 -0
- package/Server/Services/IncidentMeasurementService.ts +304 -0
- package/Server/Services/IncidentMeasurementValueService.ts +523 -0
- package/Server/Services/IncidentService.ts +51 -0
- package/Server/Services/IncidentStateTimelineService.ts +54 -0
- package/Server/Services/Index.ts +30 -0
- package/Server/Services/LlmCostBudgetService.ts +93 -0
- package/Server/Services/LlmModelPriceService.ts +202 -0
- package/Server/Services/LogAggregationService.ts +1106 -2
- package/Server/Services/LogCountBaselineService.ts +163 -0
- package/Server/Services/MutableMetricService.ts +30 -9
- package/Server/Services/ScheduledMaintenanceMeasurementService.ts +311 -0
- package/Server/Services/ScheduledMaintenanceMeasurementValueService.ts +546 -0
- package/Server/Services/ScheduledMaintenanceService.ts +53 -0
- package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +188 -1
- package/Server/Services/SecurityEventService.ts +104 -0
- package/Server/Services/SpanCountBaselineService.ts +173 -0
- package/Server/Services/StatusPageService.ts +4 -3
- package/Server/Services/TelemetryUsageBillingService.ts +17 -2
- package/Server/Services/UserService.ts +284 -51
- package/Server/Services/UserTotpAuthService.ts +27 -86
- package/Server/Services/UserWebAuthnService.ts +26 -86
- package/Server/Types/Billing/MeteredPlan/AllMeteredPlans.ts +14 -0
- package/Server/Types/Database/Permissions/TenantPermission.ts +10 -0
- package/Server/Types/Database/QueryHelper.ts +1 -1
- package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +1 -1
- package/Server/Utils/AI/Toolbox/Index.ts +6 -0
- package/Server/Utils/AI/Toolbox/SecurityEventTools.ts +349 -0
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +13 -4
- package/Server/Utils/Attribution.ts +130 -34
- package/Server/Utils/Marketing/ConversionUploadProvider.ts +98 -16
- package/Server/Utils/Marketing/Providers/GoogleAds.ts +70 -7
- package/Server/Utils/Marketing/Providers/LinkedIn.ts +54 -16
- package/Server/Utils/Marketing/Providers/Meta.ts +52 -18
- package/Server/Utils/Marketing/Providers/MicrosoftAds.ts +37 -10
- package/Server/Utils/Marketing/Providers/Reddit.ts +45 -15
- package/Server/Utils/Measurement/MeasurementDefinitionValidator.ts +209 -0
- package/Server/Utils/Measurement/MeasurementMetricWriter.ts +189 -0
- package/Server/Utils/Monitor/Criteria/CountAnomaly.ts +182 -0
- package/Server/Utils/Monitor/Criteria/LogMonitorCriteria.ts +132 -0
- package/Server/Utils/Monitor/Criteria/SecurityEventsMonitorCriteria.ts +35 -0
- package/Server/Utils/Monitor/Criteria/TraceMonitorCriteria.ts +132 -0
- package/Server/Utils/Monitor/DataToProcess.ts +2 -0
- package/Server/Utils/Monitor/MonitorCriteriaDataExtractor.ts +14 -0
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +15 -0
- package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +20 -0
- package/Server/Utils/Monitor/MonitorResource.ts +16 -0
- package/Server/Utils/Monitor/MonitorStepResourceIdentity.ts +1 -0
- package/Server/Utils/Monitor/MonitorStepsReferenceExtractor.ts +1 -0
- package/Server/Utils/SecurityEvent/DetectionRuleEvaluator.ts +548 -0
- package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.ts +262 -0
- package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.ts +212 -0
- package/Server/Utils/SecurityEvent/SecurityEventRow.ts +101 -0
- package/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.ts +635 -0
- package/Server/Utils/Telemetry/LlmCostBudgetEvaluator.ts +379 -0
- package/Server/Utils/Telemetry/LlmSpan.ts +83 -4
- package/Server/Utils/Telemetry/LogErrorPatternSql.ts +136 -0
- package/Server/Utils/UserPermission/UserPermission.ts +57 -1
- package/Tests/App/Dashboard/AlertsSideMenu.test.tsx +4 -0
- package/Tests/App/Dashboard/CreatePageEntryPointPermissions.test.tsx +306 -0
- package/Tests/App/Dashboard/DangerZone.test.tsx +11 -0
- package/Tests/App/Dashboard/IncidentsSideMenu.test.tsx +4 -0
- package/Tests/App/Dashboard/LogMonitorAttributeOperatorPreview.test.tsx +459 -0
- package/Tests/App/Dashboard/OverviewCustomFields.test.tsx +24 -0
- package/Tests/App/Dashboard/ScheduledMaintenanceSideMenu.test.tsx +6 -0
- package/Tests/Models/DatabaseModels/DomainRoleTierCoverage.test.ts +323 -0
- package/Tests/Models/DatabaseModels/ProjectSharedResources.test.ts +386 -0
- package/Tests/Server/API/LogErrorPatternAPI.test.ts +811 -0
- package/Tests/Server/API/UserAuthenticationAPI.test.ts +11 -0
- package/Tests/Server/API/UserProjectsAPI.test.ts +11 -2
- package/Tests/Server/API/UserTwoFactorAuthAdminAPI.test.ts +918 -0
- package/Tests/Server/Infrastructure/LocalCache.test.ts +149 -0
- package/Tests/Server/Services/AccessTokenProjectUserGrant.test.ts +249 -0
- package/Tests/Server/Services/AlertMeasurementService.test.ts +665 -0
- package/Tests/Server/Services/AlertMeasurementValueService.test.ts +389 -0
- package/Tests/Server/Services/DetectionRuleService.test.ts +254 -0
- package/Tests/Server/Services/IncidentMeasurementService.test.ts +394 -0
- package/Tests/Server/Services/IncidentMeasurementValueService.test.ts +816 -0
- package/Tests/Server/Services/LlmCostBudgetService.test.ts +104 -0
- package/Tests/Server/Services/LogAggregationAttributeOperators.test.ts +405 -0
- package/Tests/Server/Services/LogErrorPatternAggregation.test.ts +804 -0
- package/Tests/Server/Services/MutableMetricNameRouting.test.ts +83 -0
- package/Tests/Server/Services/ScheduledMaintenanceMeasurementService.test.ts +750 -0
- package/Tests/Server/Services/ScheduledMaintenanceMeasurementValueService.test.ts +478 -0
- package/Tests/Server/Services/UserAuthenticationService.test.ts +216 -4
- package/Tests/Server/Services/UserTwoFactorAuthAdmin.test.ts +1190 -0
- package/Tests/Server/Types/Database/Permissions/ScopedRoleSharedResourceAccess.test.ts +466 -0
- package/Tests/Server/Types/Database/QueryHelperOperators.test.ts +265 -0
- package/Tests/Server/Types/Database/QueryHelperTextAndStructuralOperators.test.ts +206 -0
- package/Tests/Server/Utils/AnalyticsDatabase/ClusterAwareSchema.test.ts +6 -0
- package/Tests/Server/Utils/AnalyticsDatabase/FractionalAttributeThreshold.test.ts +224 -0
- package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +172 -2
- package/Tests/Server/Utils/Attribution.test.ts +228 -1
- package/Tests/Server/Utils/Marketing/AdUploadableConversionTypes.test.ts +153 -92
- package/Tests/Server/Utils/Marketing/ConversionUploadProvider.test.ts +123 -6
- package/Tests/Server/Utils/Marketing/GoogleAds.test.ts +253 -1
- package/Tests/Server/Utils/Marketing/LinkedIn.test.ts +282 -0
- package/Tests/Server/Utils/Marketing/Meta.test.ts +304 -0
- package/Tests/Server/Utils/Marketing/MicrosoftAds.test.ts +263 -0
- package/Tests/Server/Utils/Marketing/Reddit.test.ts +259 -0
- package/Tests/Server/Utils/Measurement/MeasurementDefinitionValidator.test.ts +322 -0
- package/Tests/Server/Utils/Measurement/MeasurementMetricWriter.test.ts +373 -0
- package/Tests/Server/Utils/Monitor/Criteria/CountAnomaly.test.ts +221 -0
- package/Tests/Server/Utils/Monitor/Criteria/LogMonitorCriteria.test.ts +236 -0
- package/Tests/Server/Utils/Monitor/Criteria/SecurityEventsMonitorCriteria.test.ts +147 -0
- package/Tests/Server/Utils/Monitor/Criteria/TraceMonitorCriteria.test.ts +274 -0
- package/Tests/Server/Utils/Monitor/SecurityEventsMonitorDataExtractor.test.ts +93 -0
- package/Tests/Server/Utils/SecurityEvent/DetectionRuleEvaluator.test.ts +660 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsClient.test.ts +308 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPoller.test.ts +271 -0
- package/Tests/Server/Utils/SecurityEvent/SecurityEventRow.test.ts +272 -0
- package/Tests/Server/Utils/SecurityEvent/SigmaClickhouseCompiler.test.ts +511 -0
- package/Tests/Server/Utils/Telemetry/LlmCostBudgetEvaluator.test.ts +497 -0
- package/Tests/Server/Utils/Telemetry/LlmSpan.test.ts +380 -0
- package/Tests/Server/Utils/Telemetry/LogErrorPatternSql.test.ts +207 -0
- package/Tests/Server/Utils/UserPermission/ProjectUserGrant.test.ts +293 -0
- package/Tests/Types/Monitor/MonitorStepInfraMonitorUtils.test.ts +194 -0
- package/Tests/Types/Monitor/MonitorStepSecurityEventsMonitor.test.ts +201 -0
- package/Tests/Types/SecurityEvent/OcsfEventClass.test.ts +105 -0
- package/Tests/Types/SecurityEvent/OcsfSeverity.test.ts +111 -0
- package/Tests/Types/Telemetry/LlmCostCatalog.test.ts +516 -0
- package/Tests/UI/Components/ActiveFilterChipsOperatorValues.test.tsx +222 -0
- package/Tests/UI/Components/BulkActionPermissionGating.test.tsx +296 -0
- package/Tests/UI/Components/CardModelDetailPermissionGating.test.tsx +305 -0
- package/Tests/UI/Components/CustomFields/CustomFieldsDetail.test.tsx +23 -0
- package/Tests/UI/Components/DictionaryFilterOperatorDisplay.test.ts +239 -0
- package/Tests/UI/Components/DisabledButtonTooltip.test.tsx +267 -0
- package/Tests/UI/Components/DuplicateModel.test.tsx +37 -0
- package/Tests/UI/Components/ModelCardPermissionGating.test.tsx +348 -0
- package/Tests/UI/Components/ModelDelete.test.tsx +25 -1
- package/Tests/UI/Components/ModelFormCreatePermission.test.tsx +342 -0
- package/Tests/UI/Components/ModelTable/BaseModelTablePermissionGating.test.tsx +680 -0
- package/Tests/UI/Components/OrderedStatesListCreateGate.test.tsx +174 -0
- package/Tests/UI/Components/SavedViewsDropdownSearch.test.tsx +1127 -0
- package/Tests/UI/Components/SavedViewsFacetSection.test.tsx +958 -0
- package/Tests/UI/Components/SavedViewsList.test.ts +699 -0
- package/Tests/UI/Components/SavedViewsSidebarIntegration.test.tsx +640 -0
- package/Tests/UI/Components/SavedViewsSurfaceParity.test.tsx +679 -0
- package/Tests/UI/Components/TableViewLoadFailure.test.tsx +248 -0
- package/Tests/UI/Types/UseComponentOutsideClick.test.tsx +226 -0
- package/Tests/UI/Utils/PermissionGate.test.ts +467 -0
- package/Tests/Utils/Measurement/MeasurementEvaluator.test.ts +639 -0
- package/Tests/Utils/Monitor/MonitorSummarySnapshotUtil.test.ts +1 -0
- package/Tests/Utils/Monitor/NetworkDeviceLinkRuleUtil.test.ts +285 -0
- package/Tests/Utils/NetworkDevice/DeviceHealthStateUtil.test.ts +433 -0
- package/Tests/Utils/SecurityEvent/GenericNormalizer.test.ts +204 -0
- package/Tests/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.test.ts +252 -0
- package/Tests/Utils/SecurityEvent/NormalizerHelpers.test.ts +274 -0
- package/Tests/Utils/SecurityEvent/OcsfNormalizer.test.ts +208 -0
- package/Tests/Utils/SecurityEvent/SecurityEventNormalizer.test.ts +172 -0
- package/Tests/Utils/SecurityEvent/SigmaRuleParser.test.ts +471 -0
- package/Tests/Utils/SecurityEvent/UdmNormalizer.test.ts +361 -0
- package/Tests/Utils/Telemetry/CrossSignalScope.test.ts +82 -0
- package/Tests/Utils/Telemetry/LogErrorPattern.test.ts +463 -0
- package/Types/AI/AIChatTypes.ts +1 -0
- package/Types/Alerts/AlertMeasurementAnchorType.ts +14 -0
- package/Types/Alerts/AlertStateRole.ts +11 -0
- package/Types/AnalyticsDatabase/AnalyticsTableName.ts +16 -0
- package/Types/Dashboard/Chart/ChartType.ts +1 -0
- package/Types/Dashboard/DashboardComponentType.ts +2 -0
- package/Types/Dashboard/DashboardComponents/DashboardSecurityEventsFlowComponent.ts +18 -0
- package/Types/Dashboard/DashboardComponents/DashboardSecurityEventsListComponent.ts +16 -0
- package/Types/Incident/IncidentMeasurementAnchorType.ts +34 -0
- package/Types/Incident/IncidentStateRole.ts +14 -0
- package/Types/Marketing/Attribution.ts +75 -0
- package/Types/Marketing/MarketingConversion.ts +37 -7
- package/Types/Measurement/MeasurementAggregationType.ts +43 -0
- package/Types/Measurement/MeasurementAnchorKind.ts +23 -0
- package/Types/Measurement/MeasurementOccurrence.ts +14 -0
- package/Types/Measurement/MeasurementStatus.ts +34 -0
- package/Types/MeteredPlan/ProductType.ts +1 -0
- package/Types/Monitor/CriteriaFilter.ts +3 -0
- package/Types/Monitor/MonitorCriteriaInstance.ts +64 -0
- package/Types/Monitor/MonitorStep.ts +28 -0
- package/Types/Monitor/MonitorStepSecurityEventsMonitor.ts +100 -0
- package/Types/Monitor/MonitorType.ts +20 -0
- package/Types/Monitor/SecurityEventsMonitor/SecurityEventsMonitorResponse.ts +17 -0
- package/Types/Permission.ts +399 -2
- package/Types/ScheduledMaintenance/ScheduledMaintenanceMeasurementAnchorType.ts +20 -0
- package/Types/ScheduledMaintenance/ScheduledMaintenanceStateRole.ts +16 -0
- package/Types/SecurityEvent/NormalizedSecurityEvent.ts +92 -0
- package/Types/SecurityEvent/OcsfEventClass.ts +205 -0
- package/Types/SecurityEvent/OcsfSeverity.ts +88 -0
- package/Types/SecurityEvent/SecurityEventFormat.ts +23 -0
- package/Types/SecurityEvent/SigmaRule.ts +70 -0
- package/Types/Telemetry/LlmConventions.ts +20 -1
- package/Types/Telemetry/LlmCostCatalog.ts +557 -0
- package/Types/Telemetry/ServiceType.ts +1 -0
- package/Types/Telemetry/TelemetryRetentionConfig.ts +5 -1
- package/Types/Telemetry/TelemetryType.ts +1 -0
- package/Types/TwoFactorAuthStatus.ts +42 -0
- package/Types/UserAuthenticationStatus.ts +18 -0
- package/UI/Components/ActionButton/ActionButtonSchema.ts +7 -0
- package/UI/Components/BulkUpdate/BulkArchiveActions.tsx +35 -2
- package/UI/Components/BulkUpdate/BulkLabelActions.tsx +34 -1
- package/UI/Components/BulkUpdate/BulkOwnerActions.tsx +45 -1
- package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +3 -0
- package/UI/Components/Button/Button.tsx +37 -2
- package/UI/Components/Card/Card.tsx +6 -0
- package/UI/Components/CustomFields/CustomFieldsDetail.tsx +24 -1
- package/UI/Components/Dictionary/DictionaryFilterOperator.ts +57 -0
- package/UI/Components/DuplicateModel/DuplicateModel.tsx +16 -0
- package/UI/Components/Forms/ModelForm.tsx +54 -2
- package/UI/Components/List/ListRow.tsx +6 -0
- package/UI/Components/LogsViewer/components/ActiveFilterChips.tsx +34 -7
- package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +7 -40
- package/UI/Components/LogsViewer/components/SavedViewsDropdown.tsx +155 -3
- package/UI/Components/ModelDelete/ModelDelete.tsx +20 -0
- package/UI/Components/ModelDetail/CardModelDetail.tsx +36 -23
- package/UI/Components/ModelList/ModelList.tsx +21 -2
- package/UI/Components/ModelList/StaticModelList.tsx +11 -0
- package/UI/Components/ModelTable/BaseModelTable.tsx +168 -62
- package/UI/Components/ModelTable/ModelTable.tsx +16 -10
- package/UI/Components/ModelTable/TableView.tsx +36 -4
- package/UI/Components/MoreMenu/MoreMenuItem.tsx +36 -4
- package/UI/Components/OrderedStatesList/Item.tsx +6 -0
- package/UI/Components/OrderedStatesList/OrderedStatesList.tsx +81 -47
- package/UI/Components/ResetObjectID/ResetObjectID.tsx +16 -0
- package/UI/Components/SavedViews/SavedViewsFacetSection.tsx +242 -0
- package/UI/Components/SavedViews/SavedViewsList.ts +168 -0
- package/UI/Components/SavedViews/SavedViewsSearchInput.tsx +46 -0
- package/UI/Components/SavedViews/SavedViewsShowMoreButton.tsx +44 -0
- package/UI/Components/Table/TableRow.tsx +12 -0
- package/UI/Components/TelemetryViewer/components/SavedViewsDropdown.tsx +152 -2
- package/UI/Types/UseComponentOutsideClick.ts +47 -0
- package/UI/Utils/PermissionGate.ts +277 -0
- package/Utils/Dashboard/Components/DashboardSecurityEventsFlowComponent.ts +84 -0
- package/Utils/Dashboard/Components/DashboardSecurityEventsListComponent.ts +110 -0
- package/Utils/Dashboard/Components/Index.ts +14 -0
- package/Utils/Measurement/MeasurementEvaluator.ts +359 -0
- package/Utils/Monitor/MonitorSummarySnapshotUtil.ts +1 -0
- package/Utils/Monitor/NetworkDeviceLinkRuleUtil.ts +93 -11
- package/Utils/NetworkDevice/DeviceHealthStateUtil.ts +231 -0
- package/Utils/SecurityEvent/GenericNormalizer.ts +208 -0
- package/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.ts +206 -0
- package/Utils/SecurityEvent/NormalizerHelpers.ts +263 -0
- package/Utils/SecurityEvent/OcsfNormalizer.ts +194 -0
- package/Utils/SecurityEvent/SecurityEventNormalizer.ts +82 -0
- package/Utils/SecurityEvent/Sigma/SigmaRuleParser.ts +579 -0
- package/Utils/SecurityEvent/UdmNormalizer.ts +442 -0
- package/Utils/Telemetry/CrossSignalScope.ts +46 -9
- package/Utils/Telemetry/LogErrorPattern.ts +350 -0
- package/build/dist/Models/AnalyticsModels/Index.js +10 -0
- package/build/dist/Models/AnalyticsModels/Index.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/LogCountBaseline.js +159 -0
- package/build/dist/Models/AnalyticsModels/LogCountBaseline.js.map +1 -0
- package/build/dist/Models/AnalyticsModels/SecurityEvent.js +596 -0
- package/build/dist/Models/AnalyticsModels/SecurityEvent.js.map +1 -0
- package/build/dist/Models/AnalyticsModels/Span.js +24 -1
- package/build/dist/Models/AnalyticsModels/Span.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/SpanCountBaseline.js +181 -0
- package/build/dist/Models/AnalyticsModels/SpanCountBaseline.js.map +1 -0
- package/build/dist/Models/DatabaseModels/Alert.js +43 -0
- package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertMeasurement.js +1143 -0
- package/build/dist/Models/DatabaseModels/AlertMeasurement.js.map +1 -0
- package/build/dist/Models/DatabaseModels/AlertMeasurementValue.js +523 -0
- package/build/dist/Models/DatabaseModels/AlertMeasurementValue.js.map +1 -0
- package/build/dist/Models/DatabaseModels/AlertStateTimeline.js +9 -2
- package/build/dist/Models/DatabaseModels/AlertStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/BillingPaymentMethod.js +10 -11
- package/build/dist/Models/DatabaseModels/BillingPaymentMethod.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DetectionRule.js +517 -0
- package/build/dist/Models/DatabaseModels/DetectionRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/GoogleSecOpsConnection.js +410 -0
- package/build/dist/Models/DatabaseModels/GoogleSecOpsConnection.js.map +1 -0
- package/build/dist/Models/DatabaseModels/Incident.js +43 -0
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentMeasurement.js +1143 -0
- package/build/dist/Models/DatabaseModels/IncidentMeasurement.js.map +1 -0
- package/build/dist/Models/DatabaseModels/IncidentMeasurementValue.js +523 -0
- package/build/dist/Models/DatabaseModels/IncidentMeasurementValue.js.map +1 -0
- package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js +9 -2
- package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncomingCallPolicyLabelRule.js +36 -0
- package/build/dist/Models/DatabaseModels/IncomingCallPolicyLabelRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncomingCallPolicyOwnerRule.js +42 -0
- package/build/dist/Models/DatabaseModels/IncomingCallPolicyOwnerRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +20 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Label.js +11 -0
- package/build/dist/Models/DatabaseModels/Label.js.map +1 -1
- package/build/dist/Models/DatabaseModels/LlmCostBudget.js +681 -0
- package/build/dist/Models/DatabaseModels/LlmCostBudget.js.map +1 -0
- package/build/dist/Models/DatabaseModels/LlmModelPrice.js +503 -0
- package/build/dist/Models/DatabaseModels/LlmModelPrice.js.map +1 -0
- package/build/dist/Models/DatabaseModels/LogSavedView.js +87 -0
- package/build/dist/Models/DatabaseModels/LogSavedView.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MarketingConversion.js +224 -8
- package/build/dist/Models/DatabaseModels/MarketingConversion.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MetricSavedView.js +59 -0
- package/build/dist/Models/DatabaseModels/MetricSavedView.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyLabelRule.js +36 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyLabelRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyOwnerRule.js +42 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyOwnerRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLabelRule.js +36 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLabelRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerRule.js +42 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Probe.js +21 -0
- package/build/dist/Models/DatabaseModels/Probe.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RunbookLabelRule.js +36 -0
- package/build/dist/Models/DatabaseModels/RunbookLabelRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RunbookOwnerRule.js +42 -0
- package/build/dist/Models/DatabaseModels/RunbookOwnerRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceMeasurement.js +1133 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceMeasurement.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceMeasurementValue.js +515 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceMeasurementValue.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js +9 -2
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageLabelRule.js +36 -0
- package/build/dist/Models/DatabaseModels/StatusPageLabelRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageOwnerRule.js +42 -0
- package/build/dist/Models/DatabaseModels/StatusPageOwnerRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TableView.js +15 -0
- package/build/dist/Models/DatabaseModels/TableView.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Team.js +13 -0
- package/build/dist/Models/DatabaseModels/Team.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TeamMember.js +8 -0
- package/build/dist/Models/DatabaseModels/TeamMember.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TraceSavedView.js +52 -0
- package/build/dist/Models/DatabaseModels/TraceSavedView.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkflowLabelRule.js +36 -0
- package/build/dist/Models/DatabaseModels/WorkflowLabelRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkflowOwnerRule.js +42 -0
- package/build/dist/Models/DatabaseModels/WorkflowOwnerRule.js.map +1 -1
- package/build/dist/Server/API/TelemetryAPI.js +136 -0
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/API/UserAPI.js +75 -2
- package/build/dist/Server/API/UserAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +29 -1
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788000000000-AddDetectionRuleAndGoogleSecOpsConnection.js +36 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788000000000-AddDetectionRuleAndGoogleSecOpsConnection.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788100000000-AddMeasurements.js +162 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788100000000-AddMeasurements.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788200000000-AddLlmCostBudget.js +40 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788200000000-AddLlmCostBudget.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788300000000-AddLlmModelPrice.js +22 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788300000000-AddLlmModelPrice.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788400000000-AddMarketingConversionAttribution.js +52 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788400000000-AddMarketingConversionAttribution.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788500000000-RemoveLlmCostBudgetAlertColumns.js +38 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788500000000-RemoveLlmCostBudgetAlertColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/IdentityRateLimit.js +491 -0
- package/build/dist/Server/Middleware/IdentityRateLimit.js.map +1 -0
- package/build/dist/Server/Services/AccessTokenService.js +13 -2
- package/build/dist/Server/Services/AccessTokenService.js.map +1 -1
- package/build/dist/Server/Services/AlertMeasurementService.js +250 -0
- package/build/dist/Server/Services/AlertMeasurementService.js.map +1 -0
- package/build/dist/Server/Services/AlertMeasurementValueService.js +400 -0
- package/build/dist/Server/Services/AlertMeasurementValueService.js.map +1 -0
- package/build/dist/Server/Services/AlertService.js +37 -0
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/AlertStateTimelineService.js +48 -0
- package/build/dist/Server/Services/AlertStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/DetectionRuleService.js +50 -0
- package/build/dist/Server/Services/DetectionRuleService.js.map +1 -0
- package/build/dist/Server/Services/GoogleSecOpsConnectionService.js +55 -0
- package/build/dist/Server/Services/GoogleSecOpsConnectionService.js.map +1 -0
- package/build/dist/Server/Services/IncidentMeasurementService.js +252 -0
- package/build/dist/Server/Services/IncidentMeasurementService.js.map +1 -0
- package/build/dist/Server/Services/IncidentMeasurementValueService.js +413 -0
- package/build/dist/Server/Services/IncidentMeasurementValueService.js.map +1 -0
- package/build/dist/Server/Services/IncidentService.js +41 -0
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/IncidentStateTimelineService.js +50 -0
- package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +30 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/LlmCostBudgetService.js +89 -0
- package/build/dist/Server/Services/LlmCostBudgetService.js.map +1 -0
- package/build/dist/Server/Services/LlmModelPriceService.js +163 -0
- package/build/dist/Server/Services/LlmModelPriceService.js.map +1 -0
- package/build/dist/Server/Services/LogAggregationService.js +709 -0
- package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
- package/build/dist/Server/Services/LogCountBaselineService.js +112 -0
- package/build/dist/Server/Services/LogCountBaselineService.js.map +1 -0
- package/build/dist/Server/Services/MutableMetricService.js +28 -9
- package/build/dist/Server/Services/MutableMetricService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceMeasurementService.js +254 -0
- package/build/dist/Server/Services/ScheduledMaintenanceMeasurementService.js.map +1 -0
- package/build/dist/Server/Services/ScheduledMaintenanceMeasurementValueService.js +434 -0
- package/build/dist/Server/Services/ScheduledMaintenanceMeasurementValueService.js.map +1 -0
- package/build/dist/Server/Services/ScheduledMaintenanceService.js +37 -0
- package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +134 -0
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/SecurityEventService.js +64 -0
- package/build/dist/Server/Services/SecurityEventService.js.map +1 -0
- package/build/dist/Server/Services/SpanCountBaselineService.js +122 -0
- package/build/dist/Server/Services/SpanCountBaselineService.js.map +1 -0
- package/build/dist/Server/Services/StatusPageService.js +4 -3
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryUsageBillingService.js +15 -3
- package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +262 -42
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Services/UserTotpAuthService.js +0 -73
- package/build/dist/Server/Services/UserTotpAuthService.js.map +1 -1
- package/build/dist/Server/Services/UserWebAuthnService.js +1 -73
- package/build/dist/Server/Services/UserWebAuthnService.js.map +1 -1
- package/build/dist/Server/Types/Billing/MeteredPlan/AllMeteredPlans.js +13 -0
- package/build/dist/Server/Types/Billing/MeteredPlan/AllMeteredPlans.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/TenantPermission.js +10 -0
- package/build/dist/Server/Types/Database/Permissions/TenantPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryHelper.js +1 -1
- package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/Index.js +3 -0
- package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/SecurityEventTools.js +257 -0
- package/build/dist/Server/Utils/AI/Toolbox/SecurityEventTools.js.map +1 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +13 -4
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
- package/build/dist/Server/Utils/Attribution.js +77 -33
- package/build/dist/Server/Utils/Attribution.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js +46 -9
- package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js +52 -8
- package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js +41 -17
- package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/Meta.js +42 -18
- package/build/dist/Server/Utils/Marketing/Providers/Meta.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js +24 -11
- package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/Reddit.js +34 -15
- package/build/dist/Server/Utils/Marketing/Providers/Reddit.js.map +1 -1
- package/build/dist/Server/Utils/Measurement/MeasurementDefinitionValidator.js +109 -0
- package/build/dist/Server/Utils/Measurement/MeasurementDefinitionValidator.js.map +1 -0
- package/build/dist/Server/Utils/Measurement/MeasurementMetricWriter.js +131 -0
- package/build/dist/Server/Utils/Measurement/MeasurementMetricWriter.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/Criteria/CountAnomaly.js +111 -0
- package/build/dist/Server/Utils/Monitor/Criteria/CountAnomaly.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/Criteria/LogMonitorCriteria.js +84 -1
- package/build/dist/Server/Utils/Monitor/Criteria/LogMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/SecurityEventsMonitorCriteria.js +35 -0
- package/build/dist/Server/Utils/Monitor/Criteria/SecurityEventsMonitorCriteria.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/Criteria/TraceMonitorCriteria.js +84 -1
- package/build/dist/Server/Utils/Monitor/Criteria/TraceMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaDataExtractor.js +7 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaDataExtractor.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +12 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +9 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +10 -0
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js +6 -5
- package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js +404 -0
- package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js +145 -0
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js +176 -0
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/SecurityEventRow.js +69 -0
- package/build/dist/Server/Utils/SecurityEvent/SecurityEventRow.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js +493 -0
- package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/LlmCostBudgetEvaluator.js +317 -0
- package/build/dist/Server/Utils/Telemetry/LlmCostBudgetEvaluator.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/LlmSpan.js +56 -4
- package/build/dist/Server/Utils/Telemetry/LlmSpan.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/LogErrorPatternSql.js +112 -0
- package/build/dist/Server/Utils/Telemetry/LogErrorPatternSql.js.map +1 -0
- package/build/dist/Server/Utils/UserPermission/UserPermission.js +46 -1
- package/build/dist/Server/Utils/UserPermission/UserPermission.js.map +1 -1
- package/build/dist/Types/AI/AIChatTypes.js +1 -0
- package/build/dist/Types/AI/AIChatTypes.js.map +1 -1
- package/build/dist/Types/Alerts/AlertMeasurementAnchorType.js +15 -0
- package/build/dist/Types/Alerts/AlertMeasurementAnchorType.js.map +1 -0
- package/build/dist/Types/Alerts/AlertStateRole.js +12 -0
- package/build/dist/Types/Alerts/AlertStateRole.js.map +1 -0
- package/build/dist/Types/AnalyticsDatabase/AnalyticsTableName.js +16 -0
- package/build/dist/Types/AnalyticsDatabase/AnalyticsTableName.js.map +1 -1
- package/build/dist/Types/Dashboard/Chart/ChartType.js +1 -0
- package/build/dist/Types/Dashboard/Chart/ChartType.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponentType.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardSecurityEventsFlowComponent.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardSecurityEventsFlowComponent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardSecurityEventsListComponent.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardSecurityEventsListComponent.js.map +1 -0
- package/build/dist/Types/Incident/IncidentMeasurementAnchorType.js +29 -0
- package/build/dist/Types/Incident/IncidentMeasurementAnchorType.js.map +1 -0
- package/build/dist/Types/Incident/IncidentStateRole.js +15 -0
- package/build/dist/Types/Incident/IncidentStateRole.js.map +1 -0
- package/build/dist/Types/Marketing/Attribution.js +68 -0
- package/build/dist/Types/Marketing/Attribution.js.map +1 -0
- package/build/dist/Types/Marketing/MarketingConversion.js +36 -7
- package/build/dist/Types/Marketing/MarketingConversion.js.map +1 -1
- package/build/dist/Types/Measurement/MeasurementAggregationType.js +40 -0
- package/build/dist/Types/Measurement/MeasurementAggregationType.js.map +1 -0
- package/build/dist/Types/Measurement/MeasurementAnchorKind.js +22 -0
- package/build/dist/Types/Measurement/MeasurementAnchorKind.js.map +1 -0
- package/build/dist/Types/Measurement/MeasurementOccurrence.js +15 -0
- package/build/dist/Types/Measurement/MeasurementOccurrence.js.map +1 -0
- package/build/dist/Types/Measurement/MeasurementStatus.js +32 -0
- package/build/dist/Types/Measurement/MeasurementStatus.js.map +1 -0
- package/build/dist/Types/MeteredPlan/ProductType.js +1 -0
- package/build/dist/Types/MeteredPlan/ProductType.js.map +1 -1
- package/build/dist/Types/Monitor/CriteriaFilter.js +2 -0
- package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +59 -0
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStep.js +17 -0
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStepSecurityEventsMonitor.js +64 -0
- package/build/dist/Types/Monitor/MonitorStepSecurityEventsMonitor.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorType.js +19 -0
- package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
- package/build/dist/Types/Monitor/SecurityEventsMonitor/SecurityEventsMonitorResponse.js +2 -0
- package/build/dist/Types/Monitor/SecurityEventsMonitor/SecurityEventsMonitorResponse.js.map +1 -0
- package/build/dist/Types/Permission.js +347 -2
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/ScheduledMaintenance/ScheduledMaintenanceMeasurementAnchorType.js +19 -0
- package/build/dist/Types/ScheduledMaintenance/ScheduledMaintenanceMeasurementAnchorType.js.map +1 -0
- package/build/dist/Types/ScheduledMaintenance/ScheduledMaintenanceStateRole.js +17 -0
- package/build/dist/Types/ScheduledMaintenance/ScheduledMaintenanceStateRole.js.map +1 -0
- package/build/dist/Types/SecurityEvent/NormalizedSecurityEvent.js +2 -0
- package/build/dist/Types/SecurityEvent/NormalizedSecurityEvent.js.map +1 -0
- package/build/dist/Types/SecurityEvent/OcsfEventClass.js +181 -0
- package/build/dist/Types/SecurityEvent/OcsfEventClass.js.map +1 -0
- package/build/dist/Types/SecurityEvent/OcsfSeverity.js +81 -0
- package/build/dist/Types/SecurityEvent/OcsfSeverity.js.map +1 -0
- package/build/dist/Types/SecurityEvent/SecurityEventFormat.js +21 -0
- package/build/dist/Types/SecurityEvent/SecurityEventFormat.js.map +1 -0
- package/build/dist/Types/SecurityEvent/SigmaRule.js +16 -0
- package/build/dist/Types/SecurityEvent/SigmaRule.js.map +1 -0
- package/build/dist/Types/Telemetry/LlmConventions.js +19 -1
- package/build/dist/Types/Telemetry/LlmConventions.js.map +1 -1
- package/build/dist/Types/Telemetry/LlmCostCatalog.js +484 -0
- package/build/dist/Types/Telemetry/LlmCostCatalog.js.map +1 -0
- package/build/dist/Types/Telemetry/ServiceType.js +1 -0
- package/build/dist/Types/Telemetry/ServiceType.js.map +1 -1
- package/build/dist/Types/Telemetry/TelemetryRetentionConfig.js.map +1 -1
- package/build/dist/Types/Telemetry/TelemetryType.js +1 -0
- package/build/dist/Types/Telemetry/TelemetryType.js.map +1 -1
- package/build/dist/Types/TwoFactorAuthStatus.js +41 -0
- package/build/dist/Types/TwoFactorAuthStatus.js.map +1 -0
- package/build/dist/UI/Components/BulkUpdate/BulkArchiveActions.js +16 -2
- package/build/dist/UI/Components/BulkUpdate/BulkArchiveActions.js.map +1 -1
- package/build/dist/UI/Components/BulkUpdate/BulkLabelActions.js +15 -1
- package/build/dist/UI/Components/BulkUpdate/BulkLabelActions.js.map +1 -1
- package/build/dist/UI/Components/BulkUpdate/BulkOwnerActions.js +20 -1
- package/build/dist/UI/Components/BulkUpdate/BulkOwnerActions.js.map +1 -1
- package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +1 -1
- package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
- package/build/dist/UI/Components/Button/Button.js +22 -1
- package/build/dist/UI/Components/Button/Button.js.map +1 -1
- package/build/dist/UI/Components/Card/Card.js +1 -1
- package/build/dist/UI/Components/Card/Card.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +14 -1
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
- package/build/dist/UI/Components/Dictionary/DictionaryFilterOperator.js +47 -0
- package/build/dist/UI/Components/Dictionary/DictionaryFilterOperator.js.map +1 -1
- package/build/dist/UI/Components/DuplicateModel/DuplicateModel.js +8 -0
- package/build/dist/UI/Components/DuplicateModel/DuplicateModel.js.map +1 -1
- package/build/dist/UI/Components/Forms/ModelForm.js +40 -1
- package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
- package/build/dist/UI/Components/List/ListRow.js +4 -1
- package/build/dist/UI/Components/List/ListRow.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js +32 -7
- package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +2 -17
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/SavedViewsDropdown.js +93 -4
- package/build/dist/UI/Components/LogsViewer/components/SavedViewsDropdown.js.map +1 -1
- package/build/dist/UI/Components/ModelDelete/ModelDelete.js +13 -0
- package/build/dist/UI/Components/ModelDelete/ModelDelete.js.map +1 -1
- package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +27 -11
- package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
- package/build/dist/UI/Components/ModelList/ModelList.js +13 -2
- package/build/dist/UI/Components/ModelList/ModelList.js.map +1 -1
- package/build/dist/UI/Components/ModelList/StaticModelList.js +4 -1
- package/build/dist/UI/Components/ModelList/StaticModelList.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +101 -40
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/ModelTable.js +9 -7
- package/build/dist/UI/Components/ModelTable/ModelTable.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/TableView.js +32 -4
- package/build/dist/UI/Components/ModelTable/TableView.js.map +1 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +19 -2
- package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
- package/build/dist/UI/Components/OrderedStatesList/Item.js +4 -1
- package/build/dist/UI/Components/OrderedStatesList/Item.js.map +1 -1
- package/build/dist/UI/Components/OrderedStatesList/OrderedStatesList.js +33 -32
- package/build/dist/UI/Components/OrderedStatesList/OrderedStatesList.js.map +1 -1
- package/build/dist/UI/Components/ResetObjectID/ResetObjectID.js +8 -0
- package/build/dist/UI/Components/ResetObjectID/ResetObjectID.js.map +1 -1
- package/build/dist/UI/Components/SavedViews/SavedViewsFacetSection.js +98 -0
- package/build/dist/UI/Components/SavedViews/SavedViewsFacetSection.js.map +1 -0
- package/build/dist/UI/Components/SavedViews/SavedViewsList.js +99 -0
- package/build/dist/UI/Components/SavedViews/SavedViewsList.js.map +1 -0
- package/build/dist/UI/Components/SavedViews/SavedViewsSearchInput.js +17 -0
- package/build/dist/UI/Components/SavedViews/SavedViewsSearchInput.js.map +1 -0
- package/build/dist/UI/Components/SavedViews/SavedViewsShowMoreButton.js +15 -0
- package/build/dist/UI/Components/SavedViews/SavedViewsShowMoreButton.js.map +1 -0
- package/build/dist/UI/Components/Table/TableRow.js +8 -2
- package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/SavedViewsDropdown.js +93 -4
- package/build/dist/UI/Components/TelemetryViewer/components/SavedViewsDropdown.js.map +1 -1
- package/build/dist/UI/Types/UseComponentOutsideClick.js +39 -0
- package/build/dist/UI/Types/UseComponentOutsideClick.js.map +1 -1
- package/build/dist/UI/Utils/PermissionGate.js +164 -0
- package/build/dist/UI/Utils/PermissionGate.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardSecurityEventsFlowComponent.js +70 -0
- package/build/dist/Utils/Dashboard/Components/DashboardSecurityEventsFlowComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardSecurityEventsListComponent.js +92 -0
- package/build/dist/Utils/Dashboard/Components/DashboardSecurityEventsListComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/Index.js +8 -0
- package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
- package/build/dist/Utils/Measurement/MeasurementEvaluator.js +210 -0
- package/build/dist/Utils/Measurement/MeasurementEvaluator.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorSummarySnapshotUtil.js +1 -0
- package/build/dist/Utils/Monitor/MonitorSummarySnapshotUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js +51 -10
- package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js.map +1 -1
- package/build/dist/Utils/NetworkDevice/DeviceHealthStateUtil.js +108 -0
- package/build/dist/Utils/NetworkDevice/DeviceHealthStateUtil.js.map +1 -0
- package/build/dist/Utils/SecurityEvent/GenericNormalizer.js +176 -0
- package/build/dist/Utils/SecurityEvent/GenericNormalizer.js.map +1 -0
- package/build/dist/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.js +146 -0
- package/build/dist/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.js.map +1 -0
- package/build/dist/Utils/SecurityEvent/NormalizerHelpers.js +196 -0
- package/build/dist/Utils/SecurityEvent/NormalizerHelpers.js.map +1 -0
- package/build/dist/Utils/SecurityEvent/OcsfNormalizer.js +136 -0
- package/build/dist/Utils/SecurityEvent/OcsfNormalizer.js.map +1 -0
- package/build/dist/Utils/SecurityEvent/SecurityEventNormalizer.js +64 -0
- package/build/dist/Utils/SecurityEvent/SecurityEventNormalizer.js.map +1 -0
- package/build/dist/Utils/SecurityEvent/Sigma/SigmaRuleParser.js +388 -0
- package/build/dist/Utils/SecurityEvent/Sigma/SigmaRuleParser.js.map +1 -0
- package/build/dist/Utils/SecurityEvent/UdmNormalizer.js +328 -0
- package/build/dist/Utils/SecurityEvent/UdmNormalizer.js.map +1 -0
- package/build/dist/Utils/Telemetry/CrossSignalScope.js +29 -7
- package/build/dist/Utils/Telemetry/CrossSignalScope.js.map +1 -1
- package/build/dist/Utils/Telemetry/LogErrorPattern.js +284 -0
- package/build/dist/Utils/Telemetry/LogErrorPattern.js.map +1 -0
- package/package.json +3 -1
|
@@ -0,0 +1,918 @@
|
|
|
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 BadDataException from "../../../Types/Exception/BadDataException";
|
|
13
|
+
import NotFoundException from "../../../Types/Exception/NotFoundException";
|
|
14
|
+
import { JSONObject } from "../../../Types/JSON";
|
|
15
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
16
|
+
import TwoFactorAuthStatus from "../../../Types/TwoFactorAuthStatus";
|
|
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 two master-admin endpoints that let an operator drive somebody else's
|
|
29
|
+
* two factor auth, plus the status payload the page reads before it draws the
|
|
30
|
+
* buttons.
|
|
31
|
+
*
|
|
32
|
+
* POST /user/:userId/set-two-factor-auth-required turns the REQUIREMENT on or
|
|
33
|
+
* off. It exists as its own route rather than a PUT to /user/:id carrying
|
|
34
|
+
* `enableTwoFactorAuth` because the generic write would succeed -- a master
|
|
35
|
+
* admin bypasses that column's ACL -- while quietly skipping the session
|
|
36
|
+
* revocation that makes the new requirement mean anything for people already
|
|
37
|
+
* signed in. That revocation lives in UserService.setTwoFactorAuthRequired.
|
|
38
|
+
*
|
|
39
|
+
* POST /user/:userId/reset-two-factor-auth is the lost-device escape hatch: it
|
|
40
|
+
* clears every authenticator so the user is sent back through enrolment. It
|
|
41
|
+
* deliberately does NOT clear the requirement, and it deliberately takes no
|
|
42
|
+
* body.
|
|
43
|
+
*
|
|
44
|
+
* Two properties in this file are load-bearing in a way nothing else in the
|
|
45
|
+
* suite would notice going missing:
|
|
46
|
+
*
|
|
47
|
+
* 1. Both routes are gated by the SESSION-only master-admin middleware. The
|
|
48
|
+
* instance-wide master API key variant must never appear here: a leaked
|
|
49
|
+
* static key that can read is a disclosure, but one that can switch two
|
|
50
|
+
* factor auth off across every account on the instance is an account
|
|
51
|
+
* takeover with the safety catch removed.
|
|
52
|
+
*
|
|
53
|
+
* 2. `isRequired` is checked with `typeof`, not truthiness. A truthiness
|
|
54
|
+
* check would read a missing field, a null, or the STRING "false" as
|
|
55
|
+
* "turn the requirement off" -- weakening an account's security in
|
|
56
|
+
* response to a call that was merely malformed.
|
|
57
|
+
*
|
|
58
|
+
* NotAuthorizedException maps to HTTP 422 in this codebase rather than 401 or
|
|
59
|
+
* 403, which is why these tests assert on the middleware's identity rather
|
|
60
|
+
* than on any status code: the status code is not where the protection lives.
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
jest.mock("../../../Server/Utils/Express", () => {
|
|
64
|
+
return {
|
|
65
|
+
getRouter: () => {
|
|
66
|
+
return mockRouter;
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
jest.mock("../../../Server/Utils/Response", () => {
|
|
72
|
+
return {
|
|
73
|
+
sendEntityArrayResponse: jest.fn(),
|
|
74
|
+
sendJsonArrayResponse: jest.fn(),
|
|
75
|
+
sendJsonObjectResponse: jest.fn(),
|
|
76
|
+
sendEmptySuccessResponse: jest.fn(),
|
|
77
|
+
sendEntityResponse: jest.fn(),
|
|
78
|
+
sendErrorResponse: jest.fn(),
|
|
79
|
+
sendFileResponse: jest.fn(),
|
|
80
|
+
sendFileByPath: jest.fn(),
|
|
81
|
+
setNoCacheHeaders: jest.fn(),
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const SET_REQUIRED_ROUTE: string = "/user/:userId/set-two-factor-auth-required";
|
|
86
|
+
const RESET_ROUTE: string = "/user/:userId/reset-two-factor-auth";
|
|
87
|
+
const STATUS_ROUTE: string = "/user/:userId/authentication-status";
|
|
88
|
+
|
|
89
|
+
const USER_ID: string = "00000000-0000-4000-8000-000000000001";
|
|
90
|
+
|
|
91
|
+
type RouteCallResult = {
|
|
92
|
+
thrownToNext: unknown;
|
|
93
|
+
nextCallCount: number;
|
|
94
|
+
res: ExpressResponse;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
async function callRoute(data: {
|
|
98
|
+
method: string;
|
|
99
|
+
route: string;
|
|
100
|
+
params?: Dictionary<string> | undefined;
|
|
101
|
+
query?: Dictionary<string> | undefined;
|
|
102
|
+
body?: Dictionary<unknown> | undefined;
|
|
103
|
+
}): Promise<RouteCallResult> {
|
|
104
|
+
const req: ExpressRequest = {
|
|
105
|
+
params: data.params || {},
|
|
106
|
+
query: data.query || {},
|
|
107
|
+
body: data.body || {},
|
|
108
|
+
headers: {},
|
|
109
|
+
} as unknown as ExpressRequest;
|
|
110
|
+
|
|
111
|
+
const res: ExpressResponse = {
|
|
112
|
+
send: jest.fn(),
|
|
113
|
+
json: jest.fn(),
|
|
114
|
+
setHeader: jest.fn(),
|
|
115
|
+
status: jest.fn().mockReturnThis(),
|
|
116
|
+
} as unknown as ExpressResponse;
|
|
117
|
+
|
|
118
|
+
const next: jest.Mock = jest.fn();
|
|
119
|
+
|
|
120
|
+
await mockRouter
|
|
121
|
+
.match(data.method, data.route)
|
|
122
|
+
.handlerFunction(req, res, next as unknown as NextFunction);
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
thrownToNext: next.mock.calls[0] ? next.mock.calls[0][0] : undefined,
|
|
126
|
+
nextCallCount: next.mock.calls.length,
|
|
127
|
+
res: res,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function asMock(fn: unknown): jest.Mock {
|
|
132
|
+
return fn as unknown as jest.Mock;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function sentJsonObject(): JSONObject {
|
|
136
|
+
const call: Array<unknown> | undefined = asMock(
|
|
137
|
+
Response.sendJsonObjectResponse,
|
|
138
|
+
).mock.calls[0];
|
|
139
|
+
|
|
140
|
+
return (call?.[2] as JSONObject) || {};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/*
|
|
144
|
+
* User ids that are syntactically wrong in ways the route must catch before
|
|
145
|
+
* anything downstream sees them. The SQL-shaped entries are not decoration:
|
|
146
|
+
* the id becomes part of a database query once it is past the route, so the
|
|
147
|
+
* assertion below is "rejected BEFORE the service is reached", not "rejected
|
|
148
|
+
* eventually". A route that validated after calling the service would still
|
|
149
|
+
* return an error to the caller and look correct from the outside.
|
|
150
|
+
*/
|
|
151
|
+
type MalformedUserIdCase = {
|
|
152
|
+
label: string;
|
|
153
|
+
userId: string;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const MALFORMED_USER_IDS: Array<MalformedUserIdCase> = [
|
|
157
|
+
{ label: "a word that is not a uuid", userId: "not-a-uuid" },
|
|
158
|
+
{ label: "a number with sql appended", userId: "1; DROP TABLE users" },
|
|
159
|
+
{ label: "sql instead of a uuid", userId: '\'; DROP TABLE "User"; --' },
|
|
160
|
+
{ label: "a numeric id", userId: "12345" },
|
|
161
|
+
{
|
|
162
|
+
label: "a uuid missing its last character",
|
|
163
|
+
userId: "00000000-0000-4000-8000-00000000000",
|
|
164
|
+
},
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
describe("POST /user/:userId/set-two-factor-auth-required", () => {
|
|
168
|
+
let setRequiredSpy: jest.SpyInstance;
|
|
169
|
+
|
|
170
|
+
beforeAll(() => {
|
|
171
|
+
mockRouter.routes.length = 0;
|
|
172
|
+
new UserAPI();
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
beforeEach(() => {
|
|
176
|
+
jest.clearAllMocks();
|
|
177
|
+
|
|
178
|
+
setRequiredSpy = jest
|
|
179
|
+
.spyOn(UserService, "setTwoFactorAuthRequired")
|
|
180
|
+
.mockResolvedValue(undefined);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
afterEach(() => {
|
|
184
|
+
jest.restoreAllMocks();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
type FirstArgument = { userId: ObjectID; isRequired: boolean };
|
|
188
|
+
|
|
189
|
+
const firstArgument: () => FirstArgument = (): FirstArgument => {
|
|
190
|
+
return setRequiredSpy.mock.calls[0]![0] as FirstArgument;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
test("the route is registered", () => {
|
|
194
|
+
/*
|
|
195
|
+
* A typo in the path is neither a compile error nor a failure anywhere
|
|
196
|
+
* else -- it is a 404 the Admin Dashboard surfaces as an unexplained error
|
|
197
|
+
* the first time an operator tries to mandate two factor auth.
|
|
198
|
+
*/
|
|
199
|
+
expect(() => {
|
|
200
|
+
return mockRouter.match("POST", SET_REQUIRED_ROUTE);
|
|
201
|
+
}).not.toThrow();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("only a master admin can reach it", () => {
|
|
205
|
+
/*
|
|
206
|
+
* Without this middleware every signed-in user on the instance could turn
|
|
207
|
+
* two factor auth off for every other account, which is the last step
|
|
208
|
+
* before taking one over.
|
|
209
|
+
*/
|
|
210
|
+
expect(mockRouter.match("POST", SET_REQUIRED_ROUTE).middleware).toBe(
|
|
211
|
+
MasterAdminAuthorization.isAuthorizedMasterAdminMiddleware,
|
|
212
|
+
);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("does not accept the instance-wide master API key", () => {
|
|
216
|
+
/*
|
|
217
|
+
* The master API key is a static, long-lived secret that lives in an
|
|
218
|
+
* instance's environment. It is scoped to reads on purpose: a leaked key
|
|
219
|
+
* that can READ discloses data, but a leaked key that can switch off the
|
|
220
|
+
* two factor auth requirement on every account on the instance is a
|
|
221
|
+
* takeover primitive. Swapping this middleware for the
|
|
222
|
+
* ...OrMasterApiKeyMiddleware variant is a one-word edit that no other
|
|
223
|
+
* assertion in the suite would notice, so it is checked by identity here.
|
|
224
|
+
*/
|
|
225
|
+
const violations: Array<string> = [];
|
|
226
|
+
|
|
227
|
+
const middleware: unknown = mockRouter.match(
|
|
228
|
+
"POST",
|
|
229
|
+
SET_REQUIRED_ROUTE,
|
|
230
|
+
).middleware;
|
|
231
|
+
|
|
232
|
+
if (
|
|
233
|
+
middleware ===
|
|
234
|
+
MasterAdminAuthorization.isAuthorizedMasterAdminOrMasterApiKeyMiddleware
|
|
235
|
+
) {
|
|
236
|
+
violations.push(
|
|
237
|
+
"set-two-factor-auth-required accepts the instance-wide master API key",
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (
|
|
242
|
+
middleware !== MasterAdminAuthorization.isAuthorizedMasterAdminMiddleware
|
|
243
|
+
) {
|
|
244
|
+
violations.push(
|
|
245
|
+
"set-two-factor-auth-required is not gated by the session-only master admin middleware",
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
expect(violations).toEqual([]);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test("requiring two factor auth reaches the service for the user in the path", async () => {
|
|
253
|
+
await callRoute({
|
|
254
|
+
method: "POST",
|
|
255
|
+
route: SET_REQUIRED_ROUTE,
|
|
256
|
+
params: { userId: USER_ID },
|
|
257
|
+
body: { isRequired: true },
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
expect(setRequiredSpy).toHaveBeenCalledTimes(1);
|
|
261
|
+
expect(firstArgument().userId.toString()).toBe(USER_ID);
|
|
262
|
+
expect(firstArgument().isRequired).toBe(true);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
test("no longer requiring it reaches the service as a literal false", async () => {
|
|
266
|
+
/*
|
|
267
|
+
* `false` is the value most likely to be lost on the way through: dropped
|
|
268
|
+
* by a `if (isRequired)` guard, or coerced by a helper that treats it as
|
|
269
|
+
* "not supplied". Either way the service would be called with the
|
|
270
|
+
* requirement still on, or not called at all, and the page would report
|
|
271
|
+
* success over a change that never happened.
|
|
272
|
+
*/
|
|
273
|
+
await callRoute({
|
|
274
|
+
method: "POST",
|
|
275
|
+
route: SET_REQUIRED_ROUTE,
|
|
276
|
+
params: { userId: USER_ID },
|
|
277
|
+
body: { isRequired: false },
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
expect(setRequiredSpy).toHaveBeenCalledTimes(1);
|
|
281
|
+
expect(firstArgument().userId.toString()).toBe(USER_ID);
|
|
282
|
+
expect(firstArgument().isRequired).toBe(false);
|
|
283
|
+
expect(typeof firstArgument().isRequired).toBe("boolean");
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test("passes exactly userId and isRequired to the service", async () => {
|
|
287
|
+
/*
|
|
288
|
+
* The service decides everything else -- session revocation, whether the
|
|
289
|
+
* write is a no-op. Extra keys forwarded from the request body would be a
|
|
290
|
+
* way for a caller to steer a service that was never written to be
|
|
291
|
+
* steered.
|
|
292
|
+
*/
|
|
293
|
+
await callRoute({
|
|
294
|
+
method: "POST",
|
|
295
|
+
route: SET_REQUIRED_ROUTE,
|
|
296
|
+
params: { userId: USER_ID },
|
|
297
|
+
body: { isRequired: true, isRoot: true, userId: "somebody-else" },
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
expect(Object.keys(firstArgument()).sort()).toEqual([
|
|
301
|
+
"isRequired",
|
|
302
|
+
"userId",
|
|
303
|
+
]);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test("reports success with an empty body", async () => {
|
|
307
|
+
await callRoute({
|
|
308
|
+
method: "POST",
|
|
309
|
+
route: SET_REQUIRED_ROUTE,
|
|
310
|
+
params: { userId: USER_ID },
|
|
311
|
+
body: { isRequired: true },
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
expect(Response.sendEmptySuccessResponse).toHaveBeenCalledTimes(1);
|
|
315
|
+
expect(Response.sendJsonObjectResponse).not.toHaveBeenCalled();
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
/*
|
|
319
|
+
* THE case this route's `typeof` check exists for.
|
|
320
|
+
*
|
|
321
|
+
* Every value below is something a malformed or hostile client can put in
|
|
322
|
+
* `isRequired`. Under a truthiness check -- `Boolean(req.body["isRequired"])`
|
|
323
|
+
* or `if (req.body["isRequired"])` -- a missing field, a null, a 0 and the
|
|
324
|
+
* STRING "false" all collapse to false, and the route would happily go and
|
|
325
|
+
* turn the two factor auth requirement OFF for the named account. The string
|
|
326
|
+
* "true" and an empty object collapse the other way and would turn it on for
|
|
327
|
+
* an account nobody asked about. Neither direction is acceptable, so the
|
|
328
|
+
* assertion is the same for all of them: BadDataException out, and the
|
|
329
|
+
* service never touched.
|
|
330
|
+
*/
|
|
331
|
+
type NonBooleanCase = {
|
|
332
|
+
label: string;
|
|
333
|
+
body: Dictionary<unknown>;
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
const NON_BOOLEAN_CASES: Array<NonBooleanCase> = [
|
|
337
|
+
{ label: "the field is missing entirely", body: {} },
|
|
338
|
+
{ label: "undefined", body: { isRequired: undefined } },
|
|
339
|
+
{ label: "null", body: { isRequired: null } },
|
|
340
|
+
{ label: 'the string "true"', body: { isRequired: "true" } },
|
|
341
|
+
{ label: 'the string "false"', body: { isRequired: "false" } },
|
|
342
|
+
{ label: "the number 1", body: { isRequired: 1 } },
|
|
343
|
+
{ label: "the number 0", body: { isRequired: 0 } },
|
|
344
|
+
{ label: "an empty object", body: { isRequired: {} } },
|
|
345
|
+
{ label: "an empty array", body: { isRequired: [] } },
|
|
346
|
+
{ label: "an array holding a boolean", body: { isRequired: [false] } },
|
|
347
|
+
{
|
|
348
|
+
label: "an object with a boolean-ish valueOf",
|
|
349
|
+
body: { isRequired: { valueOf: false } },
|
|
350
|
+
},
|
|
351
|
+
];
|
|
352
|
+
|
|
353
|
+
test.each(NON_BOOLEAN_CASES)(
|
|
354
|
+
"refuses to act when isRequired is $label",
|
|
355
|
+
async (testCase: NonBooleanCase): Promise<void> => {
|
|
356
|
+
const result: RouteCallResult = await callRoute({
|
|
357
|
+
method: "POST",
|
|
358
|
+
route: SET_REQUIRED_ROUTE,
|
|
359
|
+
params: { userId: USER_ID },
|
|
360
|
+
body: testCase.body,
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
const violations: Array<string> = [];
|
|
364
|
+
|
|
365
|
+
if (setRequiredSpy.mock.calls.length > 0) {
|
|
366
|
+
violations.push(
|
|
367
|
+
`the service was called with a non-boolean isRequired (${testCase.label})`,
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (!(result.thrownToNext instanceof BadDataException)) {
|
|
372
|
+
violations.push(
|
|
373
|
+
`a non-boolean isRequired (${testCase.label}) did not produce a BadDataException`,
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (result.nextCallCount !== 1) {
|
|
378
|
+
violations.push(
|
|
379
|
+
`next() was called ${result.nextCallCount} times, expected exactly 1`,
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (asMock(Response.sendEmptySuccessResponse).mock.calls.length > 0) {
|
|
384
|
+
violations.push(
|
|
385
|
+
`a non-boolean isRequired (${testCase.label}) was reported as a success`,
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
expect(violations).toEqual([]);
|
|
390
|
+
},
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
test("the non-boolean table is not empty", () => {
|
|
394
|
+
/*
|
|
395
|
+
* A `test.each` over an empty table registers no tests and the suite still
|
|
396
|
+
* goes green. This guard makes a table that ever gets emptied -- by a bad
|
|
397
|
+
* merge, or by a refactor that moves the cases elsewhere -- fail loudly
|
|
398
|
+
* instead of silently removing the protection above.
|
|
399
|
+
*/
|
|
400
|
+
expect(NON_BOOLEAN_CASES.length).toBeGreaterThan(5);
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
test("rejects a missing user id without writing anything", async () => {
|
|
404
|
+
const result: RouteCallResult = await callRoute({
|
|
405
|
+
method: "POST",
|
|
406
|
+
route: SET_REQUIRED_ROUTE,
|
|
407
|
+
params: {},
|
|
408
|
+
body: { isRequired: true },
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
expect(setRequiredSpy).not.toHaveBeenCalled();
|
|
412
|
+
expect(result.nextCallCount).toBe(1);
|
|
413
|
+
expect(result.thrownToNext).toBeInstanceOf(BadDataException);
|
|
414
|
+
expect((result.thrownToNext as BadDataException).message).toBe(
|
|
415
|
+
"User ID is required",
|
|
416
|
+
);
|
|
417
|
+
expect(Response.sendEmptySuccessResponse).not.toHaveBeenCalled();
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
test("rejects a whitespace-only user id without writing anything", async () => {
|
|
421
|
+
/*
|
|
422
|
+
* " " is truthy, so a bare `if (!userIdParam)` written against the raw
|
|
423
|
+
* param would let it through and hand the service an ObjectID built from
|
|
424
|
+
* blank space.
|
|
425
|
+
*/
|
|
426
|
+
const result: RouteCallResult = await callRoute({
|
|
427
|
+
method: "POST",
|
|
428
|
+
route: SET_REQUIRED_ROUTE,
|
|
429
|
+
params: { userId: " " },
|
|
430
|
+
body: { isRequired: true },
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
expect(setRequiredSpy).not.toHaveBeenCalled();
|
|
434
|
+
expect(result.nextCallCount).toBe(1);
|
|
435
|
+
expect(result.thrownToNext).toBeInstanceOf(BadDataException);
|
|
436
|
+
expect(Response.sendEmptySuccessResponse).not.toHaveBeenCalled();
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
test.each(MALFORMED_USER_IDS)(
|
|
440
|
+
"rejects $label before the service is reached",
|
|
441
|
+
async (testCase: MalformedUserIdCase): Promise<void> => {
|
|
442
|
+
const result: RouteCallResult = await callRoute({
|
|
443
|
+
method: "POST",
|
|
444
|
+
route: SET_REQUIRED_ROUTE,
|
|
445
|
+
params: { userId: testCase.userId },
|
|
446
|
+
body: { isRequired: true },
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
const violations: Array<string> = [];
|
|
450
|
+
|
|
451
|
+
if (setRequiredSpy.mock.calls.length > 0) {
|
|
452
|
+
violations.push(`the service was reached with ${testCase.label}`);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (!(result.thrownToNext instanceof BadDataException)) {
|
|
456
|
+
violations.push(`${testCase.label} did not produce a BadDataException`);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (asMock(Response.sendEmptySuccessResponse).mock.calls.length > 0) {
|
|
460
|
+
violations.push(`${testCase.label} was reported as a success`);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
expect(violations).toEqual([]);
|
|
464
|
+
},
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
test("passes a not-found failure on rather than reporting success", async () => {
|
|
468
|
+
/*
|
|
469
|
+
* "No such user" reported as a 200 would tell an operator that two factor
|
|
470
|
+
* auth is now mandatory for an account that does not exist -- and they
|
|
471
|
+
* would stop looking for the typo in the id they pasted.
|
|
472
|
+
*/
|
|
473
|
+
const writeError: NotFoundException = new NotFoundException(
|
|
474
|
+
"User not found.",
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
setRequiredSpy.mockRejectedValue(writeError);
|
|
478
|
+
|
|
479
|
+
const result: RouteCallResult = await callRoute({
|
|
480
|
+
method: "POST",
|
|
481
|
+
route: SET_REQUIRED_ROUTE,
|
|
482
|
+
params: { userId: USER_ID },
|
|
483
|
+
body: { isRequired: true },
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
expect(result.thrownToNext).toBe(writeError);
|
|
487
|
+
expect(result.nextCallCount).toBe(1);
|
|
488
|
+
expect(Response.sendEmptySuccessResponse).not.toHaveBeenCalled();
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
test("passes an unexpected failure on rather than reporting success", async () => {
|
|
492
|
+
/*
|
|
493
|
+
* The service revokes sessions as part of this write. A database error
|
|
494
|
+
* midway through must not be swallowed: reported as success, the page
|
|
495
|
+
* would say the requirement is on while the sessions that predate it are
|
|
496
|
+
* still valid.
|
|
497
|
+
*/
|
|
498
|
+
const unexpected: Error = new Error("connection terminated unexpectedly");
|
|
499
|
+
|
|
500
|
+
setRequiredSpy.mockRejectedValue(unexpected);
|
|
501
|
+
|
|
502
|
+
const result: RouteCallResult = await callRoute({
|
|
503
|
+
method: "POST",
|
|
504
|
+
route: SET_REQUIRED_ROUTE,
|
|
505
|
+
params: { userId: USER_ID },
|
|
506
|
+
body: { isRequired: false },
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
expect(result.thrownToNext).toBe(unexpected);
|
|
510
|
+
expect(result.nextCallCount).toBe(1);
|
|
511
|
+
expect(Response.sendEmptySuccessResponse).not.toHaveBeenCalled();
|
|
512
|
+
});
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
describe("POST /user/:userId/reset-two-factor-auth", () => {
|
|
516
|
+
let resetSpy: jest.SpyInstance;
|
|
517
|
+
|
|
518
|
+
beforeAll(() => {
|
|
519
|
+
mockRouter.routes.length = 0;
|
|
520
|
+
new UserAPI();
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
beforeEach(() => {
|
|
524
|
+
jest.clearAllMocks();
|
|
525
|
+
|
|
526
|
+
resetSpy = jest
|
|
527
|
+
.spyOn(UserService, "resetTwoFactorAuth")
|
|
528
|
+
.mockResolvedValue(undefined);
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
afterEach(() => {
|
|
532
|
+
jest.restoreAllMocks();
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
test("the route is registered", () => {
|
|
536
|
+
expect(() => {
|
|
537
|
+
return mockRouter.match("POST", RESET_ROUTE);
|
|
538
|
+
}).not.toThrow();
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
test("only a master admin can reach it", () => {
|
|
542
|
+
/*
|
|
543
|
+
* Reset clears every authenticator the user has. Reachable by any
|
|
544
|
+
* signed-in user, it would be a one-request way to strip the second factor
|
|
545
|
+
* off somebody else's account and leave only their password between an
|
|
546
|
+
* attacker and it.
|
|
547
|
+
*/
|
|
548
|
+
expect(mockRouter.match("POST", RESET_ROUTE).middleware).toBe(
|
|
549
|
+
MasterAdminAuthorization.isAuthorizedMasterAdminMiddleware,
|
|
550
|
+
);
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
test("does not accept the instance-wide master API key", () => {
|
|
554
|
+
/*
|
|
555
|
+
* Same reasoning as the sibling route, and slightly sharper here: a static
|
|
556
|
+
* environment secret that can wipe every account's authenticators removes
|
|
557
|
+
* two factor auth from the whole instance without anybody signing in.
|
|
558
|
+
*/
|
|
559
|
+
const violations: Array<string> = [];
|
|
560
|
+
|
|
561
|
+
const middleware: unknown = mockRouter.match(
|
|
562
|
+
"POST",
|
|
563
|
+
RESET_ROUTE,
|
|
564
|
+
).middleware;
|
|
565
|
+
|
|
566
|
+
if (
|
|
567
|
+
middleware ===
|
|
568
|
+
MasterAdminAuthorization.isAuthorizedMasterAdminOrMasterApiKeyMiddleware
|
|
569
|
+
) {
|
|
570
|
+
violations.push(
|
|
571
|
+
"reset-two-factor-auth accepts the instance-wide master API key",
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
if (
|
|
576
|
+
middleware !== MasterAdminAuthorization.isAuthorizedMasterAdminMiddleware
|
|
577
|
+
) {
|
|
578
|
+
violations.push(
|
|
579
|
+
"reset-two-factor-auth is not gated by the session-only master admin middleware",
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
expect(violations).toEqual([]);
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
test("resets the user in the path", async () => {
|
|
587
|
+
await callRoute({
|
|
588
|
+
method: "POST",
|
|
589
|
+
route: RESET_ROUTE,
|
|
590
|
+
params: { userId: USER_ID },
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
expect(resetSpy).toHaveBeenCalledTimes(1);
|
|
594
|
+
expect(
|
|
595
|
+
(resetSpy.mock.calls[0]![0] as { userId: ObjectID }).userId.toString(),
|
|
596
|
+
).toBe(USER_ID);
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
test("calls the service with exactly a userId and nothing else", async () => {
|
|
600
|
+
/*
|
|
601
|
+
* Reset has no options by design -- "start again from a fresh QR code" is
|
|
602
|
+
* the whole operation. An extra key arriving here would mean the handler
|
|
603
|
+
* had started forwarding request data into a service that has no argument
|
|
604
|
+
* to receive it safely, which is how a flag like "also clear the
|
|
605
|
+
* requirement" gets smuggled in later.
|
|
606
|
+
*/
|
|
607
|
+
await callRoute({
|
|
608
|
+
method: "POST",
|
|
609
|
+
route: RESET_ROUTE,
|
|
610
|
+
params: { userId: USER_ID },
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
expect(Object.keys(resetSpy.mock.calls[0]![0] as JSONObject)).toEqual([
|
|
614
|
+
"userId",
|
|
615
|
+
]);
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
test("ignores a request body entirely", async () => {
|
|
619
|
+
/*
|
|
620
|
+
* The route takes no body on purpose: a route that ignores its body cannot
|
|
621
|
+
* be steered by one. Posting the sibling route's payload -- including the
|
|
622
|
+
* `isRequired: false` that would DOWNGRADE the account -- must change
|
|
623
|
+
* nothing about the call that goes out.
|
|
624
|
+
*/
|
|
625
|
+
await callRoute({
|
|
626
|
+
method: "POST",
|
|
627
|
+
route: RESET_ROUTE,
|
|
628
|
+
params: { userId: USER_ID },
|
|
629
|
+
body: {
|
|
630
|
+
isRequired: false,
|
|
631
|
+
enableTwoFactorAuth: false,
|
|
632
|
+
userId: "00000000-0000-4000-8000-0000000000ff",
|
|
633
|
+
},
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
expect(resetSpy).toHaveBeenCalledTimes(1);
|
|
637
|
+
|
|
638
|
+
const call: { userId: ObjectID } = resetSpy.mock.calls[0]![0] as {
|
|
639
|
+
userId: ObjectID;
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
expect(Object.keys(call)).toEqual(["userId"]);
|
|
643
|
+
expect(call.userId.toString()).toBe(USER_ID);
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
test("reports success with an empty body", async () => {
|
|
647
|
+
await callRoute({
|
|
648
|
+
method: "POST",
|
|
649
|
+
route: RESET_ROUTE,
|
|
650
|
+
params: { userId: USER_ID },
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
expect(Response.sendEmptySuccessResponse).toHaveBeenCalledTimes(1);
|
|
654
|
+
expect(Response.sendJsonObjectResponse).not.toHaveBeenCalled();
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
test("rejects a missing user id without clearing anything", async () => {
|
|
658
|
+
/*
|
|
659
|
+
* A reset aimed at nobody must not become a reset aimed at whoever the
|
|
660
|
+
* service would default to. It has to stop at the route.
|
|
661
|
+
*/
|
|
662
|
+
const result: RouteCallResult = await callRoute({
|
|
663
|
+
method: "POST",
|
|
664
|
+
route: RESET_ROUTE,
|
|
665
|
+
params: {},
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
expect(resetSpy).not.toHaveBeenCalled();
|
|
669
|
+
expect(result.nextCallCount).toBe(1);
|
|
670
|
+
expect(result.thrownToNext).toBeInstanceOf(BadDataException);
|
|
671
|
+
expect((result.thrownToNext as BadDataException).message).toBe(
|
|
672
|
+
"User ID is required",
|
|
673
|
+
);
|
|
674
|
+
expect(Response.sendEmptySuccessResponse).not.toHaveBeenCalled();
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
test("rejects a whitespace-only user id without clearing anything", async () => {
|
|
678
|
+
const result: RouteCallResult = await callRoute({
|
|
679
|
+
method: "POST",
|
|
680
|
+
route: RESET_ROUTE,
|
|
681
|
+
params: { userId: " \t " },
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
expect(resetSpy).not.toHaveBeenCalled();
|
|
685
|
+
expect(result.nextCallCount).toBe(1);
|
|
686
|
+
expect(result.thrownToNext).toBeInstanceOf(BadDataException);
|
|
687
|
+
expect(Response.sendEmptySuccessResponse).not.toHaveBeenCalled();
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
test.each(MALFORMED_USER_IDS)(
|
|
691
|
+
"rejects $label before the service is reached",
|
|
692
|
+
async (testCase: MalformedUserIdCase): Promise<void> => {
|
|
693
|
+
const result: RouteCallResult = await callRoute({
|
|
694
|
+
method: "POST",
|
|
695
|
+
route: RESET_ROUTE,
|
|
696
|
+
params: { userId: testCase.userId },
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
const violations: Array<string> = [];
|
|
700
|
+
|
|
701
|
+
if (resetSpy.mock.calls.length > 0) {
|
|
702
|
+
violations.push(`the service was reached with ${testCase.label}`);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
if (!(result.thrownToNext instanceof BadDataException)) {
|
|
706
|
+
violations.push(`${testCase.label} did not produce a BadDataException`);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
if (asMock(Response.sendEmptySuccessResponse).mock.calls.length > 0) {
|
|
710
|
+
violations.push(`${testCase.label} was reported as a success`);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
expect(violations).toEqual([]);
|
|
714
|
+
},
|
|
715
|
+
);
|
|
716
|
+
|
|
717
|
+
test("the malformed user id table is not empty", () => {
|
|
718
|
+
/*
|
|
719
|
+
* Guards both `test.each` tables above: emptied, they would register no
|
|
720
|
+
* tests at all and the file would still pass while asserting nothing about
|
|
721
|
+
* id validation.
|
|
722
|
+
*/
|
|
723
|
+
expect(MALFORMED_USER_IDS.length).toBeGreaterThan(2);
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
test("passes a not-found failure on rather than reporting success", async () => {
|
|
727
|
+
/*
|
|
728
|
+
* This is the route an operator reaches for while a locked-out colleague
|
|
729
|
+
* is on the phone. Told "done" when nothing was cleared, they would send
|
|
730
|
+
* that colleague back to a sign-in screen still asking for the code from
|
|
731
|
+
* the phone they lost.
|
|
732
|
+
*/
|
|
733
|
+
const resetError: NotFoundException = new NotFoundException(
|
|
734
|
+
"User not found.",
|
|
735
|
+
);
|
|
736
|
+
|
|
737
|
+
resetSpy.mockRejectedValue(resetError);
|
|
738
|
+
|
|
739
|
+
const result: RouteCallResult = await callRoute({
|
|
740
|
+
method: "POST",
|
|
741
|
+
route: RESET_ROUTE,
|
|
742
|
+
params: { userId: USER_ID },
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
expect(result.thrownToNext).toBe(resetError);
|
|
746
|
+
expect(result.nextCallCount).toBe(1);
|
|
747
|
+
expect(Response.sendEmptySuccessResponse).not.toHaveBeenCalled();
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
test("passes an unexpected failure on rather than reporting success", async () => {
|
|
751
|
+
/*
|
|
752
|
+
* A reset deletes rows from two tables. A failure partway through leaves
|
|
753
|
+
* the account in a state only the operator can see, so the failure has to
|
|
754
|
+
* reach them rather than being reported as a completed reset.
|
|
755
|
+
*/
|
|
756
|
+
const unexpected: Error = new Error("deadlock detected");
|
|
757
|
+
|
|
758
|
+
resetSpy.mockRejectedValue(unexpected);
|
|
759
|
+
|
|
760
|
+
const result: RouteCallResult = await callRoute({
|
|
761
|
+
method: "POST",
|
|
762
|
+
route: RESET_ROUTE,
|
|
763
|
+
params: { userId: USER_ID },
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
expect(result.thrownToNext).toBe(unexpected);
|
|
767
|
+
expect(result.nextCallCount).toBe(1);
|
|
768
|
+
expect(Response.sendEmptySuccessResponse).not.toHaveBeenCalled();
|
|
769
|
+
});
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
describe("GET /user/:userId/authentication-status two factor auth fields", () => {
|
|
773
|
+
/*
|
|
774
|
+
* The status payload gained two fields when admin-controlled MFA landed, and
|
|
775
|
+
* the Authentication page draws its buttons from them: `twoFactorAuthStatus`
|
|
776
|
+
* decides which of the three labels an operator reads, and
|
|
777
|
+
* `verifiedTwoFactorAuthMethodCount` is what makes "Enabled - Pending Setup"
|
|
778
|
+
* distinguishable from "Enabled - Configured".
|
|
779
|
+
*
|
|
780
|
+
* A field dropped or renamed on the way out of the route is not a compile
|
|
781
|
+
* error on the wire -- it arrives as `undefined` and the page renders the
|
|
782
|
+
* fallback, which for the pending-setup user is the reassuring "2FA on" for
|
|
783
|
+
* an account that in fact has no authenticator behind the requirement. That
|
|
784
|
+
* is precisely the user who cannot sign in unaided, so it is the one the
|
|
785
|
+
* page must never mislabel.
|
|
786
|
+
*/
|
|
787
|
+
let statusSpy: jest.SpyInstance;
|
|
788
|
+
|
|
789
|
+
const PENDING_SETUP_STATUS: UserAuthenticationStatus = {
|
|
790
|
+
hasPassword: true,
|
|
791
|
+
isEmailVerified: true,
|
|
792
|
+
isTwoFactorAuthEnabled: true,
|
|
793
|
+
twoFactorAuthStatus: TwoFactorAuthStatus.EnabledPendingSetup,
|
|
794
|
+
verifiedTwoFactorAuthMethodCount: 0,
|
|
795
|
+
hasPendingPasswordResetLink: false,
|
|
796
|
+
};
|
|
797
|
+
|
|
798
|
+
beforeAll(() => {
|
|
799
|
+
mockRouter.routes.length = 0;
|
|
800
|
+
new UserAPI();
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
beforeEach(() => {
|
|
804
|
+
jest.clearAllMocks();
|
|
805
|
+
|
|
806
|
+
statusSpy = jest
|
|
807
|
+
.spyOn(UserService, "getAuthenticationStatus")
|
|
808
|
+
.mockResolvedValue(PENDING_SETUP_STATUS);
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
afterEach(() => {
|
|
812
|
+
jest.restoreAllMocks();
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
test("passes the derived status through verbatim", async () => {
|
|
816
|
+
await callRoute({
|
|
817
|
+
method: "GET",
|
|
818
|
+
route: STATUS_ROUTE,
|
|
819
|
+
params: { userId: USER_ID },
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
expect(statusSpy).toHaveBeenCalledTimes(1);
|
|
823
|
+
expect(sentJsonObject()).toEqual({
|
|
824
|
+
hasPassword: true,
|
|
825
|
+
isEmailVerified: true,
|
|
826
|
+
isTwoFactorAuthEnabled: true,
|
|
827
|
+
twoFactorAuthStatus: TwoFactorAuthStatus.EnabledPendingSetup,
|
|
828
|
+
verifiedTwoFactorAuthMethodCount: 0,
|
|
829
|
+
hasPendingPasswordResetLink: false,
|
|
830
|
+
});
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
test("sends the status as the enum's machine value, not a display string", async () => {
|
|
834
|
+
/*
|
|
835
|
+
* The labels an operator reads are i18n keys resolved in the browser. If
|
|
836
|
+
* the route ever started sending "Enabled - Pending Setup" instead of
|
|
837
|
+
* "EnabledPendingSetup", the page's comparison against the enum would stop
|
|
838
|
+
* matching and every account would fall through to the default label.
|
|
839
|
+
*/
|
|
840
|
+
await callRoute({
|
|
841
|
+
method: "GET",
|
|
842
|
+
route: STATUS_ROUTE,
|
|
843
|
+
params: { userId: USER_ID },
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
expect(sentJsonObject()["twoFactorAuthStatus"]).toBe("EnabledPendingSetup");
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
test("keeps a zero method count rather than dropping it", async () => {
|
|
850
|
+
/*
|
|
851
|
+
* Zero is the count that matters and the one most easily lost: a
|
|
852
|
+
* serializer that omits falsy values would turn "no authenticator has been
|
|
853
|
+
* set up" into an absent field, which the page cannot tell apart from a
|
|
854
|
+
* count it failed to load.
|
|
855
|
+
*/
|
|
856
|
+
await callRoute({
|
|
857
|
+
method: "GET",
|
|
858
|
+
route: STATUS_ROUTE,
|
|
859
|
+
params: { userId: USER_ID },
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
const payload: JSONObject = sentJsonObject();
|
|
863
|
+
|
|
864
|
+
expect(Object.keys(payload)).toContain("verifiedTwoFactorAuthMethodCount");
|
|
865
|
+
expect(payload["verifiedTwoFactorAuthMethodCount"]).toBe(0);
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
test("reports a configured account with its real method count", async () => {
|
|
869
|
+
/*
|
|
870
|
+
* The other end of the tri-state. Reported as the pending-setup value, an
|
|
871
|
+
* operator would reset a user who was perfectly fine and lock them out
|
|
872
|
+
* themselves.
|
|
873
|
+
*/
|
|
874
|
+
statusSpy.mockResolvedValue({
|
|
875
|
+
hasPassword: false,
|
|
876
|
+
isEmailVerified: true,
|
|
877
|
+
isTwoFactorAuthEnabled: true,
|
|
878
|
+
twoFactorAuthStatus: TwoFactorAuthStatus.EnabledConfigured,
|
|
879
|
+
verifiedTwoFactorAuthMethodCount: 2,
|
|
880
|
+
hasPendingPasswordResetLink: false,
|
|
881
|
+
} as UserAuthenticationStatus);
|
|
882
|
+
|
|
883
|
+
await callRoute({
|
|
884
|
+
method: "GET",
|
|
885
|
+
route: STATUS_ROUTE,
|
|
886
|
+
params: { userId: USER_ID },
|
|
887
|
+
});
|
|
888
|
+
|
|
889
|
+
const payload: JSONObject = sentJsonObject();
|
|
890
|
+
|
|
891
|
+
expect(payload["twoFactorAuthStatus"]).toBe(
|
|
892
|
+
TwoFactorAuthStatus.EnabledConfigured,
|
|
893
|
+
);
|
|
894
|
+
expect(payload["verifiedTwoFactorAuthMethodCount"]).toBe(2);
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
test("sets the no-cache headers before the payload goes out", async () => {
|
|
898
|
+
/*
|
|
899
|
+
* Headers written after the body has been sent are ignored, so the ORDER
|
|
900
|
+
* is the protection, not merely that both calls happened. Whether one
|
|
901
|
+
* named user has two factor auth configured is not a fact a shared cache
|
|
902
|
+
* may keep, and it is stale the instant either action on this page runs.
|
|
903
|
+
*/
|
|
904
|
+
await callRoute({
|
|
905
|
+
method: "GET",
|
|
906
|
+
route: STATUS_ROUTE,
|
|
907
|
+
params: { userId: USER_ID },
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
const noCacheOrder: number = asMock(Response.setNoCacheHeaders).mock
|
|
911
|
+
.invocationCallOrder[0]!;
|
|
912
|
+
const sendOrder: number = asMock(Response.sendJsonObjectResponse).mock
|
|
913
|
+
.invocationCallOrder[0]!;
|
|
914
|
+
|
|
915
|
+
expect(Response.setNoCacheHeaders).toHaveBeenCalledTimes(1);
|
|
916
|
+
expect(noCacheOrder).toBeLessThan(sendOrder);
|
|
917
|
+
});
|
|
918
|
+
});
|