@oneuptime/common 12.0.7 → 12.0.9
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/MetricItemAggMV1mByK8sCluster.ts +1 -1
- package/Models/AnalyticsModels/MetricItemAggMV1mByService.ts +1 -1
- package/Models/DatabaseModels/AIConversation.ts +32 -0
- package/Models/DatabaseModels/AIConversationMessage.ts +41 -0
- package/Models/DatabaseModels/AlertEpisodeMember.ts +27 -0
- package/Models/DatabaseModels/IncidentEpisodeMember.ts +28 -0
- package/Models/DatabaseModels/Index.ts +12 -4
- package/Models/DatabaseModels/{TelemetryEntity.ts → InventoryItem.ts} +184 -9
- package/Models/DatabaseModels/InventoryItemCustomField.ts +434 -0
- package/Models/DatabaseModels/{TelemetryEntityRelationship.ts → InventoryItemRelationship.ts} +33 -7
- package/Models/DatabaseModels/NetworkDevice.ts +141 -0
- package/Models/DatabaseModels/NetworkDeviceLink.ts +699 -0
- package/Models/DatabaseModels/NetworkDeviceLinkRule.ts +467 -0
- package/Models/DatabaseModels/NetworkTopologySuppression.ts +429 -0
- package/Models/DatabaseModels/OnCallDutyPolicyFeed.ts +9 -0
- package/Models/DatabaseModels/Project.ts +78 -0
- package/Models/DatabaseModels/UserCall.ts +42 -0
- package/Models/DatabaseModels/UserEmail.ts +44 -0
- package/Models/DatabaseModels/UserNotificationRule.ts +425 -55
- package/Models/DatabaseModels/UserOnCallLogTimeline.ts +24 -2
- package/Models/DatabaseModels/UserPush.ts +49 -0
- package/Models/DatabaseModels/UserSMS.ts +43 -0
- package/Models/DatabaseModels/UserTelegram.ts +59 -0
- package/Models/DatabaseModels/UserWebhook.ts +52 -0
- package/Models/DatabaseModels/UserWhatsApp.ts +41 -0
- package/Models/DatabaseModels/WorkflowLog.ts +38 -0
- package/Models/DatabaseModels/WorkflowVariable.ts +12 -0
- package/Server/API/AIChatAPI.ts +315 -1
- package/Server/API/DashboardAPI.ts +217 -1
- package/Server/API/OnCallReadinessAPI.ts +841 -0
- package/Server/API/TeamComplianceAPI.ts +69 -17
- package/Server/API/TelemetryAPI.ts +220 -12
- package/Server/API/UserAPI.ts +16 -1
- package/Server/EnvironmentConfig.ts +52 -0
- package/Server/Infrastructure/Postgres/DataSourceOptions.ts +22 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.ts +4 -4
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.ts +5 -5
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786551733814-MigrationName.ts +41 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786559879134-AddWorkflowLogStepTrace.ts +17 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786625176831-AddMonitoringMethodToNetworkDevice.ts +35 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786634985763-AddNetworkDeviceLink.ts +82 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786639512056-AddNetworkDeviceLinkRule.ts +91 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786639972982-AddNetworkTopologySuppression.ts +47 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786800000000-RenameTelemetryEntityToInventoryItem.ts +255 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786900000000-AddInventoryItemArchiveAndCustomFields.ts +107 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787000000000-AddOnCallNotificationFallbackColumns.ts +90 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787100000000-AddAIConversationPageContext.ts +39 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787200000000-AddAIChatMessageFeedback.ts +33 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787300000000-AddEpisodeMemberNotifyIndexes.ts +59 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +24 -0
- package/Server/Infrastructure/Queue.ts +78 -13
- package/Server/Middleware/MasterAdminAuthorization.ts +11 -6
- package/Server/Middleware/PublicDashboardRateLimit.ts +593 -0
- package/Server/Services/AIService.ts +7 -0
- package/Server/Services/AlertEpisodeStateTimelineService.ts +29 -0
- package/Server/Services/AlertSeverityService.ts +63 -0
- package/Server/Services/DashboardService.ts +9 -10
- package/Server/Services/DatabaseService.ts +32 -2
- package/Server/Services/IncidentEpisodeStateTimelineService.ts +29 -0
- package/Server/Services/IncidentSeverityService.ts +76 -0
- package/Server/Services/Index.ts +12 -4
- package/Server/Services/InventoryItemCustomFieldService.ts +9 -0
- package/Server/Services/{TelemetryEntityRelationshipService.ts → InventoryItemRelationshipService.ts} +7 -4
- package/Server/Services/{TelemetryEntityService.ts → InventoryItemService.ts} +203 -21
- package/Server/Services/LogAggregationService.ts +45 -8
- package/Server/Services/MetricAggregationService.ts +121 -0
- package/Server/Services/MetricService.ts +7 -7
- package/Server/Services/NetworkDeviceLinkRuleService.ts +10 -0
- package/Server/Services/NetworkDeviceLinkService.ts +84 -0
- package/Server/Services/NetworkDeviceService.ts +140 -0
- package/Server/Services/NetworkSiteService.ts +77 -25
- package/Server/Services/NetworkTopologySuppressionService.ts +84 -0
- package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +41 -29
- package/Server/Services/OnCallDutyPolicyExecutionLogService.ts +8 -0
- package/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.ts +62 -13
- package/Server/Services/OnCallDutyPolicyScheduleService.ts +61 -1
- package/Server/Services/OnCallNotificationAlertingService.ts +742 -0
- package/Server/Services/OnCallReadinessService.ts +2803 -0
- package/Server/Services/OnCallSetupReminderService.ts +955 -0
- package/Server/Services/ProfileAggregationService.ts +123 -0
- package/Server/Services/StatusPageService.ts +9 -10
- package/Server/Services/TeamComplianceService.ts +429 -252
- package/Server/Services/UserCallService.ts +26 -1
- package/Server/Services/UserEmailService.ts +26 -1
- package/Server/Services/UserNotificationRuleAdminService.ts +1183 -0
- package/Server/Services/UserNotificationRuleService.ts +3812 -333
- package/Server/Services/UserOnCallLogService.ts +561 -48
- package/Server/Services/UserPushService.ts +29 -0
- package/Server/Services/UserService.ts +11 -0
- package/Server/Services/UserSmsService.ts +26 -1
- package/Server/Services/UserTelegramService.ts +24 -1
- package/Server/Services/UserWebhookService.ts +28 -1
- package/Server/Services/UserWhatsAppService.ts +24 -1
- package/Server/Types/Database/Permissions/BasePermission.ts +19 -0
- package/Server/Types/Database/Permissions/CreatePermission.ts +164 -0
- package/Server/Types/Database/Permissions/OwnerOnlyColumnPermission.ts +340 -0
- package/Server/Types/Database/Permissions/QueryPermission.ts +48 -0
- package/Server/Types/Database/Permissions/TenantPermission.ts +8 -1
- package/Server/Types/Workflow/Components/API/Delete.ts +1 -1
- package/Server/Types/Workflow/Components/API/Get.ts +1 -1
- package/Server/Types/Workflow/Components/API/Patch.ts +1 -1
- package/Server/Types/Workflow/Components/API/Post.ts +1 -1
- package/Server/Types/Workflow/Components/API/Put.ts +1 -1
- package/Server/Types/Workflow/Components/API/Utils.ts +44 -1
- package/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.ts +29 -5
- package/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.ts +18 -10
- package/Server/Types/Workflow/Components/BaseModel/ModelArguments.ts +55 -0
- package/Server/Types/Workflow/Components/Conditions/IfElse.ts +3 -17
- package/Server/Types/Workflow/Components/Email.ts +25 -7
- package/Server/Types/Workflow/Components/JavaScript.ts +10 -3
- package/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.ts +1 -1
- package/Server/Types/Workflow/TriggerCode.ts +12 -0
- package/Server/Types/Workflow/Workflow.ts +5 -0
- package/Server/Utils/AI/Chat/ChatAgentRunner.ts +643 -48
- package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +32 -3
- package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +20 -6
- package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +7 -0
- package/Server/Utils/AI/Toolbox/AIActionTools.ts +2 -2
- package/Server/Utils/AI/Toolbox/AIMetaTools.ts +863 -0
- package/Server/Utils/AI/Toolbox/AlertTools.ts +177 -15
- package/Server/Utils/AI/Toolbox/IncidentTools.ts +191 -10
- package/Server/Utils/AI/Toolbox/Index.ts +48 -0
- package/Server/Utils/AI/Toolbox/MonitorTools.ts +298 -11
- package/Server/Utils/AI/Toolbox/NoteWriteTools.ts +295 -0
- package/Server/Utils/AI/Toolbox/OnCallTools.ts +1246 -0
- package/Server/Utils/AI/Toolbox/RunbookTools.ts +424 -0
- package/Server/Utils/AI/Toolbox/SloTools.ts +456 -0
- package/Server/Utils/AI/Toolbox/StatusPageTools.ts +559 -0
- package/Server/Utils/AI/Toolbox/TeamTools.ts +327 -0
- package/Server/Utils/AI/Toolbox/TimelineTools.ts +615 -0
- package/Server/Utils/AI/Toolbox/WorkflowProbeTools.ts +664 -0
- package/Server/Utils/ClientIp.ts +221 -0
- package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +47 -0
- package/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.ts +163 -0
- package/Server/Utils/Dashboard/PublicDashboardSloWidget.ts +147 -0
- package/Server/Utils/Express.ts +12 -17
- package/Server/Utils/LLM/LLMService.ts +85 -8
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +204 -10
- package/Server/Utils/SSRFProtection.ts +98 -23
- package/Server/Utils/StartServer.ts +12 -3
- package/Server/Utils/Telemetry/EntityRegistry.ts +205 -18
- package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +689 -0
- package/Server/Utils/Telemetry/TelemetryEntity.ts +160 -52
- package/Server/Utils/VM/VMAPI.ts +56 -8
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +7 -3
- package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +2146 -0
- package/Tests/App/Dashboard/CreateWorkflowModal.test.tsx +561 -0
- package/Tests/App/Dashboard/EscalationRuleReadiness.test.tsx +2470 -0
- package/Tests/App/Dashboard/MonitorCriteriaAttributeFilter.test.tsx +574 -0
- package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +1897 -0
- package/Tests/App/Dashboard/OnCallReadinessSurfaces.test.tsx +3606 -0
- package/Tests/App/Dashboard/OnCallRulesDeleteGuard.test.tsx +784 -0
- package/Tests/App/Dashboard/OnCallRulesTable.test.tsx +1119 -0
- package/Tests/App/Dashboard/SloWidgetFetching.test.tsx +531 -0
- package/Tests/App/Dashboard/UserSettingsSetupChecklistModel.test.ts +1312 -0
- package/Tests/App/Dashboard/UserSettingsSetupChecklistPage.test.tsx +1390 -0
- package/Tests/Models/InventoryItemModel.test.ts +174 -0
- package/Tests/Models/InventoryItemNaming.test.ts +302 -0
- package/Tests/Server/API/AIChatCancelAndFeedback.test.ts +437 -0
- package/Tests/Server/API/DashboardPublicRateLimit.test.ts +659 -0
- package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +18 -0
- package/Tests/Server/API/DashboardPublicSloAPI.test.ts +880 -0
- package/Tests/Server/API/Helpers.ts +24 -15
- package/Tests/Server/API/OnCallReadinessAPI.test.ts +2680 -0
- package/Tests/Server/API/OnCallSetupReminderAPI.test.ts +915 -0
- package/Tests/Server/API/UserProjectsAPI.test.ts +478 -4
- package/Tests/Server/Infrastructure/Postgres/EpisodeMemberNotifyIndexesMigration.test.ts +533 -0
- package/Tests/Server/Infrastructure/Postgres/InventoryItemArchiveMigration.test.ts +213 -0
- package/Tests/Server/Infrastructure/Postgres/RenameInventoryItemMigration.test.ts +432 -0
- package/Tests/Server/Infrastructure/Queue.test.ts +293 -0
- package/Tests/Server/Middleware/PublicDashboardRateLimit.test.ts +1645 -0
- package/Tests/Server/Services/AdminRuleEditGuards.test.ts +2848 -0
- package/Tests/Server/Services/DeliverNotificationForRuleExtraction.test.ts +1393 -0
- package/Tests/Server/Services/EpisodeRuleSeverityRepair.test.ts +1802 -0
- package/Tests/Server/Services/EpisodeStateTimelineNote.test.ts +304 -0
- package/Tests/Server/Services/InventoryItemDisplayName.test.ts +339 -0
- package/Tests/Server/Services/InventoryItemManualCreate.test.ts +248 -0
- package/Tests/Server/Services/IpAllowlistSpoofing.test.ts +450 -0
- package/Tests/Server/Services/LogAggregationService.test.ts +235 -1
- package/Tests/Server/Services/MetricAggregationService.test.ts +231 -0
- package/Tests/Server/Services/MetricEntityMVKeyParity.test.ts +80 -29
- package/Tests/Server/Services/MetricServiceAggregate.test.ts +30 -30
- package/Tests/Server/Services/NetworkSiteService.test.ts +18 -3
- package/Tests/Server/Services/NotificationChannelEventCoverage.test.ts +1728 -0
- package/Tests/Server/Services/NotificationDeletionImpact.test.ts +2402 -0
- package/Tests/Server/Services/OnCallDutyPolicyExecutionLogTimelineGapFeed.test.ts +394 -0
- package/Tests/Server/Services/OnCallNotificationFallback.test.ts +1744 -0
- package/Tests/Server/Services/OnCallReadinessService.test.ts +4295 -0
- package/Tests/Server/Services/OnCallSetupReminder.test.ts +1272 -0
- package/Tests/Server/Services/OnCallWeeklyReadinessDigest.test.ts +1021 -0
- package/Tests/Server/Services/ProfileAggregationService.test.ts +296 -0
- package/Tests/Server/Services/SeverityCreationRuleBackfill.test.ts +1536 -0
- package/Tests/Server/Services/SeverityRuleBackfill.test.ts +1818 -0
- package/Tests/Server/Services/TeamComplianceServiceBehaviour.test.ts +1845 -0
- package/Tests/Server/Services/UserNotificationRuleAdminGuards.test.ts +1394 -0
- package/Tests/Server/Services/UserNotificationRuleDefaultCreation.test.ts +1166 -0
- package/Tests/Server/Services/UserNotificationRuleExecuteItem.test.ts +1468 -0
- package/Tests/Server/Services/UserOnCallLogNoNotificationRules.test.ts +1457 -0
- package/Tests/Server/Types/Database/Permissions/AdminNotificationRuleAccess.test.ts +1546 -0
- package/Tests/Server/Types/Database/Permissions/CreateOwnershipScoping.test.ts +529 -0
- package/Tests/Server/Types/Database/Permissions/OwnerOnlyColumns.test.ts +1219 -0
- package/Tests/Server/Types/Database/Permissions/UserNotificationRuleScoping.test.ts +1089 -0
- package/Tests/Server/Types/Workflow/Components/ApiComponentErrorPort.test.ts +2 -1
- package/Tests/Server/Types/Workflow/Components/ApiComponentHeaders.test.ts +192 -0
- package/Tests/Server/Types/Workflow/Components/BaseModelDatabaseComponents.test.ts +190 -0
- package/Tests/Server/Types/Workflow/Components/ChatWebhookComponents.test.ts +44 -14
- package/Tests/Server/Types/Workflow/Components/Email.test.ts +151 -0
- package/Tests/Server/Types/Workflow/Components/IfElse.test.ts +98 -0
- package/Tests/Server/Types/Workflow/Components/JavaScript.test.ts +51 -0
- package/Tests/Server/Utils/AI/AIMetaTools.test.ts +586 -0
- package/Tests/Server/Utils/AI/AlertMonitorFilters.test.ts +582 -0
- package/Tests/Server/Utils/AI/ChatAgentRunner.test.ts +726 -0
- package/Tests/Server/Utils/AI/IncidentToolsFilters.test.ts +315 -0
- package/Tests/Server/Utils/AI/LLMServiceStopReason.test.ts +314 -0
- package/Tests/Server/Utils/AI/LLMServiceToolCalling.test.ts +26 -3
- package/Tests/Server/Utils/AI/NoteWriteTools.test.ts +268 -0
- package/Tests/Server/Utils/AI/ObservabilityChatPrompt.test.ts +169 -0
- package/Tests/Server/Utils/AI/OnCallTools.test.ts +664 -0
- package/Tests/Server/Utils/AI/RunbookTools.test.ts +325 -0
- package/Tests/Server/Utils/AI/SloTools.test.ts +306 -0
- package/Tests/Server/Utils/AI/StatusPageTools.test.ts +391 -0
- package/Tests/Server/Utils/AI/TeamTools.test.ts +257 -0
- package/Tests/Server/Utils/AI/TimelineTools.test.ts +472 -0
- package/Tests/Server/Utils/AI/WorkflowProbeTools.test.ts +428 -0
- package/Tests/Server/Utils/AnalyticsDatabase/QuerySettingsHelper.test.ts +152 -0
- package/Tests/Server/Utils/ClientIp.test.ts +438 -0
- package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +171 -0
- package/Tests/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.test.ts +383 -0
- package/Tests/Server/Utils/EntityRegistryRowFence.test.ts +23 -25
- package/Tests/Server/Utils/MicrosoftTeamsWebhookUrlValidation.test.ts +6 -0
- package/Tests/Server/Utils/Monitor/Criteria/DnssecMonitorCriteria.test.ts +307 -0
- package/Tests/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.test.ts +468 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorTelemetryDeepLinks.test.ts +460 -0
- package/Tests/Server/Utils/ResponseRateLimitStatusCodes.test.ts +137 -0
- package/Tests/Server/Utils/SSRFProtectionBypasses.test.ts +40 -8
- package/Tests/Server/Utils/SSRFProtectionUserInfo.test.ts +351 -0
- package/Tests/Server/Utils/Telemetry/EntityRegistryRetirement.test.ts +531 -0
- package/Tests/Server/Utils/Telemetry/InventoryEntityRegistry.test.ts +322 -0
- package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +356 -39
- package/Tests/Server/Utils/VM/VMAPISubstitution.test.ts +243 -0
- package/Tests/Types/IP/IP.test.ts +263 -0
- package/Tests/Types/IP/IPWhitelist.test.ts +197 -0
- package/Tests/Types/IP/IPv6.test.ts +12 -1
- package/Tests/Types/Monitor/SnmpOid.test.ts +64 -0
- package/Tests/Types/NetworkDevice/NetworkDeviceMonitoringMethod.test.ts +110 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilMergeAudit.test.ts +106 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilMergeDifferential.test.ts +511 -0
- package/Tests/Types/OnCallDutyPolicy/ScheduleCoverageEndToEnd.test.ts +489 -0
- package/Tests/Types/OnCallDutyPolicy/ScheduleCoverageGapTolerance.test.ts +479 -0
- package/Tests/Types/OnCallDutyPolicy/ScheduleCoverageState.test.ts +822 -0
- package/Tests/Types/SerializableObjectDictionaryImportCycle.test.ts +197 -0
- package/Tests/Types/Telemetry/EntityTypeGroups.test.ts +140 -0
- package/Tests/Types/Workflow/BaseModelComponents.test.ts +429 -0
- package/Tests/Types/Workflow/Components/BaseModel.test.ts +225 -0
- package/Tests/Types/Workflow/IntegrationCredentialMetadata.test.ts +100 -0
- package/Tests/Types/Workflow/StepTrace.test.ts +235 -0
- package/Tests/Types/Workflow/TemplateSyntax.test.ts +491 -0
- package/Tests/Types/Workflow/Templates.test.ts +1218 -0
- package/Tests/UI/Components/ActiveFilterChipsOpenRoute.test.tsx +82 -0
- package/Tests/UI/Components/ComponentsModal.test.tsx +564 -7
- package/Tests/UI/Components/CustomTimeRangeModal.test.tsx +459 -0
- package/Tests/UI/Components/DictionaryAttributeFilterRow.test.tsx +477 -0
- package/Tests/UI/Components/Forms/AnchoredFieldPopupKeyboard.test.tsx +382 -0
- package/Tests/UI/Components/Forms/ColorPickerPicking.test.tsx +569 -0
- package/Tests/UI/Components/Forms/ValidationJSON.test.ts +241 -0
- package/Tests/UI/Components/KeyboardShortcut.test.tsx +95 -0
- package/Tests/UI/Components/LogDetailsPanelCrossSignal.test.tsx +448 -0
- package/Tests/UI/Components/LogTimeRangePicker.test.tsx +42 -0
- package/Tests/UI/Components/LogsTableCrossLinks.test.tsx +263 -0
- package/Tests/UI/Components/ModalPortalDismissal.test.tsx +90 -0
- package/Tests/UI/Components/PendingProjectInvitations.test.tsx +913 -0
- package/Tests/UI/Components/SimpleLogViewer.test.tsx +228 -0
- package/Tests/UI/Components/TableRowSelectability.test.tsx +312 -0
- package/Tests/UI/Components/TelemetryTimeRangePicker.test.tsx +49 -7
- package/Tests/UI/Components/TimeRangePickerDropdown.test.tsx +325 -0
- package/Tests/UI/Components/Workflow/GraphLint.test.ts +893 -0
- package/Tests/UI/Components/Workflow/GraphLintSummary.test.ts +755 -0
- package/Tests/UI/Components/Workflow/ModelColumnEditor.test.ts +522 -0
- package/Tests/UI/Components/Workflow/ModelColumnEditorServerContract.test.ts +193 -0
- package/Tests/UI/Components/Workflow/ModelSchema.test.ts +388 -0
- package/Tests/UI/Components/Workflow/RunStatusWatcher.test.ts +210 -0
- package/Tests/UI/Components/Workflow/StepTraceViewer.test.tsx +256 -0
- package/Tests/UI/Components/Workflow/UseRunWatch.test.tsx +665 -0
- package/Tests/UI/Components/Workflow/Utils.test.ts +192 -0
- package/Tests/UI/Components/Workflow/WorkflowIssuesModal.test.tsx +485 -0
- package/Tests/UI/Components/Workflow/WorkflowLogModal.test.tsx +478 -0
- package/Tests/UI/Components/Workflow/WorkflowStatusBar.test.tsx +379 -0
- package/Tests/UI/EsbuildConfig.test.ts +607 -0
- package/Tests/UI/Monitor/MonitorStepCriteriaView.test.tsx +432 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +5 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +11 -4
- package/Tests/UI/Utils/ModelAPICreateMiscData.test.ts +94 -0
- package/Tests/UI/Utils/Platform.test.ts +147 -0
- package/Tests/UI/Utils/ProjectInvitationDisplay.test.ts +357 -0
- package/Tests/Utils/Monitor/NetworkDeviceLinkRuleUtil.test.ts +198 -0
- package/Tests/Utils/Monitor/NetworkDeviceRoleUtil.test.ts +514 -0
- package/Tests/Utils/Monitor/NetworkTopologyUtil.test.ts +727 -0
- package/Tests/Utils/Schema/AnalyticsModelSchema.test.ts +469 -0
- package/Tests/Utils/Schema/ModelSchema.test.ts +268 -0
- package/Tests/Utils/Telemetry/CrossSignalScope.test.ts +698 -0
- package/Tests/Utils/Telemetry/EntityKeyNonTelemetry.test.ts +193 -0
- package/Tests/__mocks__/bullmq.js +55 -0
- package/Types/AI/AIChatMessageStatus.ts +8 -1
- package/Types/AI/AIChatTypes.ts +12 -0
- package/Types/Database/AccessControl/OwnerOnlyColumn.ts +88 -0
- package/Types/Exception/ExceptionCode.ts +2 -0
- package/Types/Exception/ServiceUnavailableException.ts +8 -0
- package/Types/Exception/TooManyRequestsException.ts +8 -0
- package/Types/IP/IP.ts +93 -47
- package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +80 -3
- package/Types/NetworkDevice/NetworkDeviceMonitoringMethod.ts +55 -0
- package/Types/OnCallDutyPolicy/Layer.ts +203 -149
- package/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus.ts +13 -0
- package/Types/OnCallDutyPolicy/ScheduleShiftUtil.ts +155 -10
- package/Types/Permission.ts +193 -0
- package/Types/SerializableObjectDictionary.ts +133 -39
- package/Types/Telemetry/EntityRelationshipType.ts +1 -1
- package/Types/Telemetry/EntitySource.ts +40 -0
- package/Types/Telemetry/EntityType.ts +28 -1
- package/Types/Telemetry/EntityTypeGroups.ts +65 -0
- package/Types/Workflow/Component.ts +29 -0
- package/Types/Workflow/Components/API.ts +35 -0
- package/Types/Workflow/Components/BaseModel.ts +77 -27
- package/Types/Workflow/Components/Discord.ts +1 -0
- package/Types/Workflow/Components/Email.ts +12 -3
- package/Types/Workflow/Components/JavaScript.ts +7 -0
- package/Types/Workflow/Components/MicrosoftTeams.ts +3 -2
- package/Types/Workflow/Components/Slack.ts +1 -0
- package/Types/Workflow/Components/Telegram.ts +1 -0
- package/Types/Workflow/StepTrace.ts +181 -0
- package/Types/Workflow/TemplateSyntax.ts +543 -0
- package/Types/Workflow/Templates.ts +2368 -0
- package/UI/Components/Calendar/Calendar.css +43 -0
- package/UI/Components/Calendar/Calendar.tsx +8 -0
- package/UI/Components/Card/Card.tsx +2 -2
- package/UI/Components/Checkbox/Checkbox.tsx +16 -0
- package/UI/Components/Date/CustomTimeRangeModal.tsx +278 -0
- package/UI/Components/Date/TimeRangePickerDropdown.tsx +250 -0
- package/UI/Components/Dictionary/Dictionary.tsx +71 -15
- package/UI/Components/FormModal/BasicFormModal.tsx +2 -1
- package/UI/Components/Forms/Fields/ColorPicker.tsx +66 -10
- package/UI/Components/Forms/Fields/IconPicker.tsx +48 -10
- package/UI/Components/Forms/Types/Field.ts +7 -0
- package/UI/Components/Forms/Validation.ts +63 -1
- package/UI/Components/Header/HeaderIconDropdownButton.tsx +53 -3
- package/UI/Components/Input/Input.tsx +32 -3
- package/UI/Components/KeyboardShortcut/KeyboardKey.ts +185 -0
- package/UI/Components/KeyboardShortcut/KeyboardShortcut.tsx +87 -0
- package/UI/Components/LogsViewer/LogsViewer.tsx +28 -0
- package/UI/Components/LogsViewer/components/ActiveFilterChips.tsx +31 -0
- package/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.tsx +18 -18
- package/UI/Components/LogsViewer/components/LogDetailsPanel.tsx +363 -14
- package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +11 -216
- package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +11 -0
- package/UI/Components/LogsViewer/components/LogsTable.tsx +155 -12
- package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +29 -0
- package/UI/Components/LogsViewer/types.ts +23 -0
- package/UI/Components/Markdown.tsx/MarkdownEditor.tsx +9 -2
- package/UI/Components/Modal/Modal.tsx +37 -4
- package/UI/Components/Navbar/NavBarMenuModal.tsx +15 -30
- package/UI/Components/ProjectInvitations/PendingProjectInvitations.tsx +442 -0
- package/UI/Components/SimpleLogViewer/SimpleLogViewer.tsx +23 -1
- package/UI/Components/Table/Table.tsx +49 -16
- package/UI/Components/Table/TableBody.tsx +53 -28
- package/UI/Components/Table/TableHeader.tsx +13 -0
- package/UI/Components/Table/TableRow.tsx +58 -26
- package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +11 -210
- package/UI/Components/Workflow/ArgumentsForm.tsx +341 -8
- package/UI/Components/Workflow/Component.tsx +28 -26
- package/UI/Components/Workflow/ComponentReturnValueViewer.tsx +26 -0
- package/UI/Components/Workflow/ComponentSettingsModal.tsx +79 -9
- package/UI/Components/Workflow/ComponentValuePickerModal.tsx +135 -7
- package/UI/Components/Workflow/ComponentsModal.tsx +116 -22
- package/UI/Components/Workflow/DocumentationViewer.tsx +59 -9
- package/UI/Components/Workflow/GraphLint.ts +628 -0
- package/UI/Components/Workflow/GraphLintSummary.ts +390 -0
- package/UI/Components/Workflow/ModelColumnEditor.tsx +528 -0
- package/UI/Components/Workflow/ModelSchema.ts +278 -0
- package/UI/Components/Workflow/RunForm.tsx +41 -7
- package/UI/Components/Workflow/RunStatusWatcher.ts +122 -0
- package/UI/Components/Workflow/StepTraceViewer.tsx +186 -0
- package/UI/Components/Workflow/UseRunWatch.ts +212 -0
- package/UI/Components/Workflow/Utils.ts +93 -1
- package/UI/Components/Workflow/VariableModal.tsx +6 -2
- package/UI/Components/Workflow/Workflow.tsx +126 -7
- package/UI/Components/Workflow/WorkflowIssuesModal.tsx +255 -0
- package/UI/Components/Workflow/WorkflowLogModal.tsx +128 -0
- package/UI/Components/Workflow/WorkflowStatusBar.tsx +224 -0
- package/UI/Types/LayeredDismissal.ts +31 -0
- package/UI/Types/UseAnchoredFieldPopup.ts +99 -0
- package/UI/Utils/AIChatExport/ConversationMarkdown.ts +10 -0
- package/UI/Utils/ModelAPI/ModelAPI.ts +7 -1
- package/UI/Utils/Platform.ts +149 -0
- package/UI/Utils/ProjectInvitationDisplay.ts +118 -0
- package/UI/esbuild-config.js +22 -1
- package/Utils/Monitor/NetworkDeviceLinkRuleUtil.ts +187 -0
- package/Utils/Monitor/NetworkDeviceRoleUtil.ts +533 -0
- package/Utils/Monitor/NetworkTopologyUtil.ts +917 -155
- package/Utils/Telemetry/CrossSignalScope.ts +502 -0
- package/Utils/Telemetry/EntityKey.ts +71 -5
- package/Utils/Telemetry/EntityRelationship.ts +1 -1
- package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByK8sCluster.js +1 -1
- package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByService.js +1 -1
- package/build/dist/Models/DatabaseModels/AIConversation.js +32 -0
- package/build/dist/Models/DatabaseModels/AIConversation.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AIConversationMessage.js +42 -0
- package/build/dist/Models/DatabaseModels/AIConversationMessage.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertEpisodeMember.js +28 -0
- package/build/dist/Models/DatabaseModels/AlertEpisodeMember.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodeMember.js +29 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisodeMember.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +12 -4
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/{TelemetryEntity.js → InventoryItem.js} +212 -29
- package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -0
- package/build/dist/Models/DatabaseModels/InventoryItemCustomField.js +454 -0
- package/build/dist/Models/DatabaseModels/InventoryItemCustomField.js.map +1 -0
- package/build/dist/Models/DatabaseModels/{TelemetryEntityRelationship.js → InventoryItemRelationship.js} +52 -25
- package/build/dist/Models/DatabaseModels/InventoryItemRelationship.js.map +1 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +141 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js +719 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js.map +1 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceLinkRule.js +475 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceLinkRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/NetworkTopologySuppression.js +446 -0
- package/build/dist/Models/DatabaseModels/NetworkTopologySuppression.js.map +1 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyFeed.js +9 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +80 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserCall.js +46 -2
- package/build/dist/Models/DatabaseModels/UserCall.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserEmail.js +48 -2
- package/build/dist/Models/DatabaseModels/UserEmail.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserNotificationRule.js +424 -55
- package/build/dist/Models/DatabaseModels/UserNotificationRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +24 -2
- package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserPush.js +51 -1
- package/build/dist/Models/DatabaseModels/UserPush.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserSMS.js +47 -2
- package/build/dist/Models/DatabaseModels/UserSMS.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserTelegram.js +65 -3
- package/build/dist/Models/DatabaseModels/UserTelegram.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserWebhook.js +56 -2
- package/build/dist/Models/DatabaseModels/UserWebhook.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserWhatsApp.js +45 -2
- package/build/dist/Models/DatabaseModels/UserWhatsApp.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkflowLog.js +39 -0
- package/build/dist/Models/DatabaseModels/WorkflowLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkflowVariable.js +12 -0
- package/build/dist/Models/DatabaseModels/WorkflowVariable.js.map +1 -1
- package/build/dist/Server/API/AIChatAPI.js +237 -1
- package/build/dist/Server/API/AIChatAPI.js.map +1 -1
- package/build/dist/Server/API/DashboardAPI.js +165 -13
- package/build/dist/Server/API/DashboardAPI.js.map +1 -1
- package/build/dist/Server/API/OnCallReadinessAPI.js +599 -0
- package/build/dist/Server/API/OnCallReadinessAPI.js.map +1 -0
- package/build/dist/Server/API/TeamComplianceAPI.js +68 -9
- package/build/dist/Server/API/TeamComplianceAPI.js.map +1 -1
- package/build/dist/Server/API/TelemetryAPI.js +129 -18
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/API/UserAPI.js +16 -1
- package/build/dist/Server/API/UserAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +45 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js +22 -0
- package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js +4 -4
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786551733814-MigrationName.js +20 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786551733814-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786559879134-AddWorkflowLogStepTrace.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786559879134-AddWorkflowLogStepTrace.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786625176831-AddMonitoringMethodToNetworkDevice.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786625176831-AddMonitoringMethodToNetworkDevice.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786634985763-AddNetworkDeviceLink.js +39 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786634985763-AddNetworkDeviceLink.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786639512056-AddNetworkDeviceLinkRule.js +38 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786639512056-AddNetworkDeviceLinkRule.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786639972982-AddNetworkTopologySuppression.js +22 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786639972982-AddNetworkTopologySuppression.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786800000000-RenameTelemetryEntityToInventoryItem.js +150 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786800000000-RenameTelemetryEntityToInventoryItem.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786900000000-AddInventoryItemArchiveAndCustomFields.js +57 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786900000000-AddInventoryItemArchiveAndCustomFields.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787000000000-AddOnCallNotificationFallbackColumns.js +69 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787000000000-AddOnCallNotificationFallbackColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787100000000-AddAIConversationPageContext.js +32 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787100000000-AddAIConversationPageContext.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787200000000-AddAIChatMessageFeedback.js +26 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787200000000-AddAIChatMessageFeedback.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787300000000-AddEpisodeMemberNotifyIndexes.js +48 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787300000000-AddEpisodeMemberNotifyIndexes.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +24 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/Queue.js +72 -13
- package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
- package/build/dist/Server/Middleware/MasterAdminAuthorization.js +11 -6
- package/build/dist/Server/Middleware/MasterAdminAuthorization.js.map +1 -1
- package/build/dist/Server/Middleware/PublicDashboardRateLimit.js +399 -0
- package/build/dist/Server/Middleware/PublicDashboardRateLimit.js.map +1 -0
- package/build/dist/Server/Services/AIService.js +1 -0
- package/build/dist/Server/Services/AIService.js.map +1 -1
- package/build/dist/Server/Services/AlertEpisodeStateTimelineService.js +24 -3
- package/build/dist/Server/Services/AlertEpisodeStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/AlertSeverityService.js +54 -0
- package/build/dist/Server/Services/AlertSeverityService.js.map +1 -1
- package/build/dist/Server/Services/DashboardService.js +10 -9
- package/build/dist/Server/Services/DashboardService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +24 -2
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js +24 -3
- package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentSeverityService.js +67 -0
- package/build/dist/Server/Services/IncidentSeverityService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +12 -4
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/InventoryItemCustomFieldService.js +9 -0
- package/build/dist/Server/Services/InventoryItemCustomFieldService.js.map +1 -0
- package/build/dist/Server/Services/{TelemetryEntityRelationshipService.js → InventoryItemRelationshipService.js} +9 -6
- package/build/dist/Server/Services/InventoryItemRelationshipService.js.map +1 -0
- package/build/dist/Server/Services/{TelemetryEntityService.js → InventoryItemService.js} +158 -23
- package/build/dist/Server/Services/InventoryItemService.js.map +1 -0
- package/build/dist/Server/Services/LogAggregationService.js +27 -8
- package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
- package/build/dist/Server/Services/MetricAggregationService.js +80 -0
- package/build/dist/Server/Services/MetricAggregationService.js.map +1 -1
- package/build/dist/Server/Services/MetricService.js +6 -6
- package/build/dist/Server/Services/MetricService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceLinkRuleService.js +9 -0
- package/build/dist/Server/Services/NetworkDeviceLinkRuleService.js.map +1 -0
- package/build/dist/Server/Services/NetworkDeviceLinkService.js +71 -0
- package/build/dist/Server/Services/NetworkDeviceLinkService.js.map +1 -0
- package/build/dist/Server/Services/NetworkDeviceService.js +113 -0
- package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
- package/build/dist/Server/Services/NetworkSiteService.js +53 -13
- package/build/dist/Server/Services/NetworkSiteService.js.map +1 -1
- package/build/dist/Server/Services/NetworkTopologySuppressionService.js +85 -0
- package/build/dist/Server/Services/NetworkTopologySuppressionService.js.map +1 -0
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +48 -32
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js +8 -0
- package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.js +51 -12
- package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +57 -13
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallNotificationAlertingService.js +548 -0
- package/build/dist/Server/Services/OnCallNotificationAlertingService.js.map +1 -0
- package/build/dist/Server/Services/OnCallReadinessService.js +1961 -0
- package/build/dist/Server/Services/OnCallReadinessService.js.map +1 -0
- package/build/dist/Server/Services/OnCallSetupReminderService.js +738 -0
- package/build/dist/Server/Services/OnCallSetupReminderService.js.map +1 -0
- package/build/dist/Server/Services/ProfileAggregationService.js +68 -4
- package/build/dist/Server/Services/ProfileAggregationService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +11 -10
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/TeamComplianceService.js +312 -160
- package/build/dist/Server/Services/TeamComplianceService.js.map +1 -1
- package/build/dist/Server/Services/UserCallService.js +24 -1
- package/build/dist/Server/Services/UserCallService.js.map +1 -1
- package/build/dist/Server/Services/UserEmailService.js +24 -1
- package/build/dist/Server/Services/UserEmailService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleAdminService.js +858 -0
- package/build/dist/Server/Services/UserNotificationRuleAdminService.js.map +1 -0
- package/build/dist/Server/Services/UserNotificationRuleService.js +2830 -175
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/UserOnCallLogService.js +488 -43
- package/build/dist/Server/Services/UserOnCallLogService.js.map +1 -1
- package/build/dist/Server/Services/UserPushService.js +26 -0
- package/build/dist/Server/Services/UserPushService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +10 -0
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Services/UserSmsService.js +24 -1
- package/build/dist/Server/Services/UserSmsService.js.map +1 -1
- package/build/dist/Server/Services/UserTelegramService.js +22 -1
- package/build/dist/Server/Services/UserTelegramService.js.map +1 -1
- package/build/dist/Server/Services/UserWebhookService.js +25 -1
- package/build/dist/Server/Services/UserWebhookService.js.map +1 -1
- package/build/dist/Server/Services/UserWhatsAppService.js +22 -1
- package/build/dist/Server/Services/UserWhatsAppService.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/BasePermission.js +12 -1
- package/build/dist/Server/Types/Database/Permissions/BasePermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/CreatePermission.js +126 -0
- package/build/dist/Server/Types/Database/Permissions/CreatePermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/OwnerOnlyColumnPermission.js +254 -0
- package/build/dist/Server/Types/Database/Permissions/OwnerOnlyColumnPermission.js.map +1 -0
- package/build/dist/Server/Types/Database/Permissions/QueryPermission.js +47 -2
- package/build/dist/Server/Types/Database/Permissions/QueryPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/TenantPermission.js +7 -0
- package/build/dist/Server/Types/Database/Permissions/TenantPermission.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Delete.js +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Delete.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Get.js +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Get.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Patch.js +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Patch.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Post.js +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Post.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Put.js +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Put.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Utils.js +28 -0
- package/build/dist/Server/Types/Workflow/Components/API/Utils.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.js +21 -5
- package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.js +14 -10
- package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/BaseModel/ModelArguments.js +31 -0
- package/build/dist/Server/Types/Workflow/Components/BaseModel/ModelArguments.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/Conditions/IfElse.js +3 -9
- package/build/dist/Server/Types/Workflow/Components/Conditions/IfElse.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/Email.js +15 -4
- package/build/dist/Server/Types/Workflow/Components/Email.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/JavaScript.js +7 -2
- package/build/dist/Server/Types/Workflow/Components/JavaScript.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.js +1 -1
- package/build/dist/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.js.map +1 -1
- package/build/dist/Server/Types/Workflow/TriggerCode.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js +514 -56
- package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +20 -3
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +19 -6
- package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +7 -0
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/AIActionTools.js +2 -2
- package/build/dist/Server/Utils/AI/Toolbox/AIActionTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/AIMetaTools.js +692 -0
- package/build/dist/Server/Utils/AI/Toolbox/AIMetaTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/AlertTools.js +148 -12
- package/build/dist/Server/Utils/AI/Toolbox/AlertTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js +157 -10
- package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/Index.js +37 -0
- package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/MonitorTools.js +259 -14
- package/build/dist/Server/Utils/AI/Toolbox/MonitorTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/NoteWriteTools.js +235 -0
- package/build/dist/Server/Utils/AI/Toolbox/NoteWriteTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js +1000 -0
- package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/RunbookTools.js +356 -0
- package/build/dist/Server/Utils/AI/Toolbox/RunbookTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/SloTools.js +394 -0
- package/build/dist/Server/Utils/AI/Toolbox/SloTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/StatusPageTools.js +465 -0
- package/build/dist/Server/Utils/AI/Toolbox/StatusPageTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/TeamTools.js +280 -0
- package/build/dist/Server/Utils/AI/Toolbox/TeamTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/TimelineTools.js +527 -0
- package/build/dist/Server/Utils/AI/Toolbox/TimelineTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/WorkflowProbeTools.js +548 -0
- package/build/dist/Server/Utils/AI/Toolbox/WorkflowProbeTools.js.map +1 -0
- package/build/dist/Server/Utils/ClientIp.js +137 -0
- package/build/dist/Server/Utils/ClientIp.js.map +1 -0
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +38 -0
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -1
- package/build/dist/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.js +89 -0
- package/build/dist/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.js.map +1 -0
- package/build/dist/Server/Utils/Dashboard/PublicDashboardSloWidget.js +77 -0
- package/build/dist/Server/Utils/Dashboard/PublicDashboardSloWidget.js.map +1 -0
- package/build/dist/Server/Utils/Express.js +12 -12
- package/build/dist/Server/Utils/Express.js.map +1 -1
- package/build/dist/Server/Utils/LLM/LLMService.js +70 -7
- package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +121 -11
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/SSRFProtection.js +82 -21
- package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
- package/build/dist/Server/Utils/StartServer.js +12 -4
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +165 -18
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +507 -0
- package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +122 -47
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
- package/build/dist/Server/Utils/VM/VMAPI.js +51 -4
- package/build/dist/Server/Utils/VM/VMAPI.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +7 -3
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/AI/AIChatMessageStatus.js +8 -1
- package/build/dist/Types/AI/AIChatMessageStatus.js.map +1 -1
- package/build/dist/Types/AI/AIChatTypes.js +12 -0
- package/build/dist/Types/AI/AIChatTypes.js.map +1 -1
- package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js +60 -0
- package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js.map +1 -0
- package/build/dist/Types/Exception/ExceptionCode.js +2 -0
- package/build/dist/Types/Exception/ExceptionCode.js.map +1 -1
- package/build/dist/Types/Exception/ServiceUnavailableException.js +8 -0
- package/build/dist/Types/Exception/ServiceUnavailableException.js.map +1 -0
- package/build/dist/Types/Exception/TooManyRequestsException.js +8 -0
- package/build/dist/Types/Exception/TooManyRequestsException.js.map +1 -0
- package/build/dist/Types/IP/IP.js +87 -43
- package/build/dist/Types/IP/IP.js.map +1 -1
- package/build/dist/Types/NetworkDevice/NetworkDeviceMonitoringMethod.js +50 -0
- package/build/dist/Types/NetworkDevice/NetworkDeviceMonitoringMethod.js.map +1 -0
- package/build/dist/Types/OnCallDutyPolicy/Layer.js +186 -123
- package/build/dist/Types/OnCallDutyPolicy/Layer.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus.js +13 -0
- package/build/dist/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js +105 -11
- package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js.map +1 -1
- package/build/dist/Types/Permission.js +174 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/SerializableObjectDictionary.js +133 -39
- package/build/dist/Types/SerializableObjectDictionary.js.map +1 -1
- package/build/dist/Types/Telemetry/EntityRelationshipType.js +1 -1
- package/build/dist/Types/Telemetry/EntitySource.js +39 -0
- package/build/dist/Types/Telemetry/EntitySource.js.map +1 -0
- package/build/dist/Types/Telemetry/EntityType.js +28 -1
- package/build/dist/Types/Telemetry/EntityType.js.map +1 -1
- package/build/dist/Types/Telemetry/EntityTypeGroups.js +57 -0
- package/build/dist/Types/Telemetry/EntityTypeGroups.js.map +1 -0
- package/build/dist/Types/Workflow/Component.js +21 -0
- package/build/dist/Types/Workflow/Component.js.map +1 -1
- package/build/dist/Types/Workflow/Components/API.js +35 -0
- package/build/dist/Types/Workflow/Components/API.js.map +1 -1
- package/build/dist/Types/Workflow/Components/BaseModel.js +68 -26
- package/build/dist/Types/Workflow/Components/BaseModel.js.map +1 -1
- package/build/dist/Types/Workflow/Components/Discord.js +1 -0
- package/build/dist/Types/Workflow/Components/Discord.js.map +1 -1
- package/build/dist/Types/Workflow/Components/Email.js +12 -3
- package/build/dist/Types/Workflow/Components/Email.js.map +1 -1
- package/build/dist/Types/Workflow/Components/JavaScript.js +7 -0
- package/build/dist/Types/Workflow/Components/JavaScript.js.map +1 -1
- package/build/dist/Types/Workflow/Components/MicrosoftTeams.js +3 -2
- package/build/dist/Types/Workflow/Components/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/Workflow/Components/Slack.js +1 -0
- package/build/dist/Types/Workflow/Components/Slack.js.map +1 -1
- package/build/dist/Types/Workflow/Components/Telegram.js +1 -0
- package/build/dist/Types/Workflow/Components/Telegram.js.map +1 -1
- package/build/dist/Types/Workflow/StepTrace.js +104 -0
- package/build/dist/Types/Workflow/StepTrace.js.map +1 -0
- package/build/dist/Types/Workflow/TemplateSyntax.js +338 -0
- package/build/dist/Types/Workflow/TemplateSyntax.js.map +1 -0
- package/build/dist/Types/Workflow/Templates.js +2111 -0
- package/build/dist/Types/Workflow/Templates.js.map +1 -0
- package/build/dist/UI/Components/Calendar/Calendar.js +1 -1
- package/build/dist/UI/Components/Calendar/Calendar.js.map +1 -1
- package/build/dist/UI/Components/Checkbox/Checkbox.js +1 -1
- package/build/dist/UI/Components/Checkbox/Checkbox.js.map +1 -1
- package/build/dist/UI/Components/Date/CustomTimeRangeModal.js +126 -0
- package/build/dist/UI/Components/Date/CustomTimeRangeModal.js.map +1 -0
- package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js +123 -0
- package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js.map +1 -0
- package/build/dist/UI/Components/Dictionary/Dictionary.js +47 -17
- package/build/dist/UI/Components/Dictionary/Dictionary.js.map +1 -1
- package/build/dist/UI/Components/FormModal/BasicFormModal.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/ColorPicker.js +52 -13
- package/build/dist/UI/Components/Forms/Fields/ColorPicker.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/IconPicker.js +28 -12
- package/build/dist/UI/Components/Forms/Fields/IconPicker.js.map +1 -1
- package/build/dist/UI/Components/Forms/Validation.js +44 -0
- package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
- package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js +27 -4
- package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js.map +1 -1
- package/build/dist/UI/Components/Input/Input.js +13 -4
- package/build/dist/UI/Components/Input/Input.js.map +1 -1
- package/build/dist/UI/Components/KeyboardShortcut/KeyboardKey.js +163 -0
- package/build/dist/UI/Components/KeyboardShortcut/KeyboardKey.js.map +1 -0
- package/build/dist/UI/Components/KeyboardShortcut/KeyboardShortcut.js +47 -0
- package/build/dist/UI/Components/KeyboardShortcut/KeyboardShortcut.js.map +1 -0
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +4 -4
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js +11 -1
- package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.js +10 -8
- package/build/dist/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js +227 -14
- package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +5 -108
- package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +5 -0
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsTable.js +69 -5
- package/build/dist/UI/Components/LogsViewer/components/LogsTable.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +8 -0
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/types.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js +9 -2
- package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js.map +1 -1
- package/build/dist/UI/Components/Modal/Modal.js +31 -5
- package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +8 -21
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
- package/build/dist/UI/Components/ProjectInvitations/PendingProjectInvitations.js +251 -0
- package/build/dist/UI/Components/ProjectInvitations/PendingProjectInvitations.js.map +1 -0
- package/build/dist/UI/Components/SimpleLogViewer/SimpleLogViewer.js +9 -2
- package/build/dist/UI/Components/SimpleLogViewer/SimpleLogViewer.js.map +1 -1
- package/build/dist/UI/Components/Table/Table.js +27 -15
- package/build/dist/UI/Components/Table/Table.js.map +1 -1
- package/build/dist/UI/Components/Table/TableBody.js +24 -18
- package/build/dist/UI/Components/Table/TableBody.js.map +1 -1
- package/build/dist/UI/Components/Table/TableHeader.js +9 -1
- package/build/dist/UI/Components/Table/TableHeader.js.map +1 -1
- package/build/dist/UI/Components/Table/TableRow.js +29 -21
- package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +5 -105
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ArgumentsForm.js +261 -14
- package/build/dist/UI/Components/Workflow/ArgumentsForm.js.map +1 -1
- package/build/dist/UI/Components/Workflow/Component.js +20 -19
- package/build/dist/UI/Components/Workflow/Component.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ComponentReturnValueViewer.js +10 -1
- package/build/dist/UI/Components/Workflow/ComponentReturnValueViewer.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ComponentSettingsModal.js +35 -7
- package/build/dist/UI/Components/Workflow/ComponentSettingsModal.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ComponentValuePickerModal.js +57 -7
- package/build/dist/UI/Components/Workflow/ComponentValuePickerModal.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ComponentsModal.js +53 -18
- package/build/dist/UI/Components/Workflow/ComponentsModal.js.map +1 -1
- package/build/dist/UI/Components/Workflow/DocumentationViewer.js +19 -6
- package/build/dist/UI/Components/Workflow/DocumentationViewer.js.map +1 -1
- package/build/dist/UI/Components/Workflow/GraphLint.js +425 -0
- package/build/dist/UI/Components/Workflow/GraphLint.js.map +1 -0
- package/build/dist/UI/Components/Workflow/GraphLintSummary.js +231 -0
- package/build/dist/UI/Components/Workflow/GraphLintSummary.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ModelColumnEditor.js +320 -0
- package/build/dist/UI/Components/Workflow/ModelColumnEditor.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ModelSchema.js +156 -0
- package/build/dist/UI/Components/Workflow/ModelSchema.js.map +1 -0
- package/build/dist/UI/Components/Workflow/RunForm.js +22 -6
- package/build/dist/UI/Components/Workflow/RunForm.js.map +1 -1
- package/build/dist/UI/Components/Workflow/RunStatusWatcher.js +76 -0
- package/build/dist/UI/Components/Workflow/RunStatusWatcher.js.map +1 -0
- package/build/dist/UI/Components/Workflow/StepTraceViewer.js +76 -0
- package/build/dist/UI/Components/Workflow/StepTraceViewer.js.map +1 -0
- package/build/dist/UI/Components/Workflow/UseRunWatch.js +123 -0
- package/build/dist/UI/Components/Workflow/UseRunWatch.js.map +1 -0
- package/build/dist/UI/Components/Workflow/Utils.js +73 -1
- package/build/dist/UI/Components/Workflow/Utils.js.map +1 -1
- package/build/dist/UI/Components/Workflow/VariableModal.js +3 -2
- package/build/dist/UI/Components/Workflow/VariableModal.js.map +1 -1
- package/build/dist/UI/Components/Workflow/Workflow.js +92 -7
- package/build/dist/UI/Components/Workflow/Workflow.js.map +1 -1
- package/build/dist/UI/Components/Workflow/WorkflowIssuesModal.js +99 -0
- package/build/dist/UI/Components/Workflow/WorkflowIssuesModal.js.map +1 -0
- package/build/dist/UI/Components/Workflow/WorkflowLogModal.js +56 -0
- package/build/dist/UI/Components/Workflow/WorkflowLogModal.js.map +1 -0
- package/build/dist/UI/Components/Workflow/WorkflowStatusBar.js +92 -0
- package/build/dist/UI/Components/Workflow/WorkflowStatusBar.js.map +1 -0
- package/build/dist/UI/Types/LayeredDismissal.js +21 -0
- package/build/dist/UI/Types/LayeredDismissal.js.map +1 -0
- package/build/dist/UI/Types/UseAnchoredFieldPopup.js +74 -1
- package/build/dist/UI/Types/UseAnchoredFieldPopup.js.map +1 -1
- package/build/dist/UI/Utils/AIChatExport/ConversationMarkdown.js +9 -0
- package/build/dist/UI/Utils/AIChatExport/ConversationMarkdown.js.map +1 -1
- package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +1 -1
- package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
- package/build/dist/UI/Utils/Platform.js +118 -0
- package/build/dist/UI/Utils/Platform.js.map +1 -0
- package/build/dist/UI/Utils/ProjectInvitationDisplay.js +106 -0
- package/build/dist/UI/Utils/ProjectInvitationDisplay.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js +108 -0
- package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js +386 -0
- package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +661 -126
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
- package/build/dist/Utils/Telemetry/CrossSignalScope.js +328 -0
- package/build/dist/Utils/Telemetry/CrossSignalScope.js.map +1 -0
- package/build/dist/Utils/Telemetry/EntityKey.js +57 -5
- package/build/dist/Utils/Telemetry/EntityKey.js.map +1 -1
- package/build/dist/Utils/Telemetry/EntityRelationship.js +1 -1
- package/jest.config.json +1 -0
- package/package.json +1 -1
- package/build/dist/Models/DatabaseModels/TelemetryEntity.js.map +0 -1
- package/build/dist/Models/DatabaseModels/TelemetryEntityRelationship.js.map +0 -1
- package/build/dist/Server/Services/TelemetryEntityRelationshipService.js.map +0 -1
- package/build/dist/Server/Services/TelemetryEntityService.js.map +0 -1
|
@@ -0,0 +1,1394 @@
|
|
|
1
|
+
import DatabaseConfig from "../../../Server/DatabaseConfig";
|
|
2
|
+
import AuditLogService from "../../../Server/Services/AuditLogService";
|
|
3
|
+
import MailService from "../../../Server/Services/MailService";
|
|
4
|
+
import ProjectService from "../../../Server/Services/ProjectService";
|
|
5
|
+
import TeamMemberService from "../../../Server/Services/TeamMemberService";
|
|
6
|
+
import UserCallService from "../../../Server/Services/UserCallService";
|
|
7
|
+
import UserEmailService from "../../../Server/Services/UserEmailService";
|
|
8
|
+
import UserNotificationRuleAdminService from "../../../Server/Services/UserNotificationRuleAdminService";
|
|
9
|
+
import UserNotificationRuleService, {
|
|
10
|
+
ExecuteNotificationRuleOptions,
|
|
11
|
+
} from "../../../Server/Services/UserNotificationRuleService";
|
|
12
|
+
import UserOnCallLogService from "../../../Server/Services/UserOnCallLogService";
|
|
13
|
+
import UserOnCallLogTimelineService from "../../../Server/Services/UserOnCallLogTimelineService";
|
|
14
|
+
import UserPushService from "../../../Server/Services/UserPushService";
|
|
15
|
+
import UserService from "../../../Server/Services/UserService";
|
|
16
|
+
import UserSmsService from "../../../Server/Services/UserSmsService";
|
|
17
|
+
import UserTelegramService from "../../../Server/Services/UserTelegramService";
|
|
18
|
+
import UserWebhookService from "../../../Server/Services/UserWebhookService";
|
|
19
|
+
import UserWhatsAppService from "../../../Server/Services/UserWhatsAppService";
|
|
20
|
+
import CreateBy from "../../../Server/Types/Database/CreateBy";
|
|
21
|
+
import FindOneByID from "../../../Server/Types/Database/FindOneByID";
|
|
22
|
+
import { OnCreate, OnUpdate } from "../../../Server/Types/Database/Hooks";
|
|
23
|
+
import UpdateBy from "../../../Server/Types/Database/UpdateBy";
|
|
24
|
+
import logger from "../../../Server/Utils/Logger";
|
|
25
|
+
import Project from "../../../Models/DatabaseModels/Project";
|
|
26
|
+
import TeamMember from "../../../Models/DatabaseModels/TeamMember";
|
|
27
|
+
import User from "../../../Models/DatabaseModels/User";
|
|
28
|
+
import UserNotificationRule from "../../../Models/DatabaseModels/UserNotificationRule";
|
|
29
|
+
import UserOnCallLogTimeline from "../../../Models/DatabaseModels/UserOnCallLogTimeline";
|
|
30
|
+
import URL from "../../../Types/API/URL";
|
|
31
|
+
import DatabaseCommonInteractionProps from "../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
32
|
+
import Email from "../../../Types/Email";
|
|
33
|
+
import Exception from "../../../Types/Exception/Exception";
|
|
34
|
+
import Name from "../../../Types/Name";
|
|
35
|
+
import NotificationRuleType from "../../../Types/NotificationRule/NotificationRuleType";
|
|
36
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
37
|
+
import UserNotificationEventType from "../../../Types/UserNotification/UserNotificationEventType";
|
|
38
|
+
import UserNotificationStatus from "../../../Types/UserNotification/UserNotificationStatus";
|
|
39
|
+
import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
|
|
40
|
+
|
|
41
|
+
/*
|
|
42
|
+
* Phase 3 opens UserNotificationRule to project administrators so that somebody
|
|
43
|
+
* other than the owner can repair a broken on-call configuration. Every test in
|
|
44
|
+
* this file exists because opening that door, done carelessly, converts a
|
|
45
|
+
* support feature into a paging-hijack primitive.
|
|
46
|
+
*
|
|
47
|
+
* The shape of the attack is worth stating once, because it is what every
|
|
48
|
+
* assertion below is really about. A rule row has TWO halves that nothing in
|
|
49
|
+
* the ORM, the permission layer or any screen ever compares:
|
|
50
|
+
*
|
|
51
|
+
* userId -> whose on-call pages select this row
|
|
52
|
+
* user<X>Id -> which address the page is delivered to
|
|
53
|
+
*
|
|
54
|
+
* Set the first to the victim and the second to something the attacker
|
|
55
|
+
* controls, and the victim's pages arrive at the attacker's endpoint. The
|
|
56
|
+
* victim's rules page still says they are covered. The on-call log still says a
|
|
57
|
+
* page went out. Nothing anywhere reports a problem — the page simply reaches
|
|
58
|
+
* the wrong human, during the incident where it mattered.
|
|
59
|
+
*
|
|
60
|
+
* So the tests here are adversarial by construction. The happy paths are
|
|
61
|
+
* present only to prove the guards do not simply reject everything (a guard
|
|
62
|
+
* that blocks legitimate repair gets deleted by the next engineer, and then
|
|
63
|
+
* there is no guard at all); the weight is on the inputs an attacker would
|
|
64
|
+
* actually send:
|
|
65
|
+
*
|
|
66
|
+
* R1 a target user id from ANOTHER project, presented by a caller who is a
|
|
67
|
+
* legitimate administrator of THIS one.
|
|
68
|
+
* R3 a method FK pointing at somebody else's row — on create, on update, in
|
|
69
|
+
* the FK slot, in the relation slot, and on all seven channels, because
|
|
70
|
+
* a guard covering six of seven covers none.
|
|
71
|
+
* R3 on update specifically: a body that LIES about the rule's owner, to
|
|
72
|
+
* check that the owner is re-read from the database rather than believed.
|
|
73
|
+
* R6 an audit trail and an owner notification keyed on the server-resolved
|
|
74
|
+
* actor versus the PERSISTED owner, never on anything in the body.
|
|
75
|
+
* D-i-D a row that is already hijacked, to check it is inert at delivery time
|
|
76
|
+
* even though no write path should have produced it.
|
|
77
|
+
*
|
|
78
|
+
* No database is touched: every service the guards consult is a jest.spyOn
|
|
79
|
+
* stub, and the protected hooks are reached through a structural cast, the same
|
|
80
|
+
* way the neighbouring characterisation tests reach theirs.
|
|
81
|
+
*/
|
|
82
|
+
|
|
83
|
+
const PROJECT_ID: ObjectID = new ObjectID(
|
|
84
|
+
"11111111-1111-4111-8111-111111111111",
|
|
85
|
+
);
|
|
86
|
+
const OTHER_PROJECT_ID: ObjectID = new ObjectID(
|
|
87
|
+
"1111aaaa-1111-4111-8111-111111111111",
|
|
88
|
+
);
|
|
89
|
+
const ADMIN_USER_ID: ObjectID = new ObjectID(
|
|
90
|
+
"22222222-2222-4222-8222-222222222222",
|
|
91
|
+
);
|
|
92
|
+
const VICTIM_USER_ID: ObjectID = new ObjectID(
|
|
93
|
+
"33333333-3333-4333-8333-333333333333",
|
|
94
|
+
);
|
|
95
|
+
const OUTSIDER_USER_ID: ObjectID = new ObjectID(
|
|
96
|
+
"44444444-4444-4444-8444-444444444444",
|
|
97
|
+
);
|
|
98
|
+
const METHOD_ID: ObjectID = new ObjectID(
|
|
99
|
+
"55555555-5555-4555-8555-555555555555",
|
|
100
|
+
);
|
|
101
|
+
const RULE_ID: ObjectID = new ObjectID("66666666-6666-4666-8666-666666666666");
|
|
102
|
+
const TEAM_MEMBER_ID: ObjectID = new ObjectID(
|
|
103
|
+
"77777777-7777-4777-8777-777777777777",
|
|
104
|
+
);
|
|
105
|
+
const LOG_ID: ObjectID = new ObjectID("88888888-8888-4888-8888-888888888888");
|
|
106
|
+
const TIMELINE_ID: ObjectID = new ObjectID(
|
|
107
|
+
"99999999-9999-4999-8999-999999999999",
|
|
108
|
+
);
|
|
109
|
+
const INCIDENT_ID: ObjectID = new ObjectID(
|
|
110
|
+
"aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
/*
|
|
114
|
+
* The seven channels, each with the FK column a rule carries it in, the
|
|
115
|
+
* relation slot that is the same write spelled differently, the service the
|
|
116
|
+
* guard must consult, and the word the rejection message has to contain.
|
|
117
|
+
*
|
|
118
|
+
* Driving every ownership test off this one table is the point: the guard's
|
|
119
|
+
* only real failure mode is forgetting a channel, and a table-driven test
|
|
120
|
+
* cannot forget one without the count assertion below noticing.
|
|
121
|
+
*/
|
|
122
|
+
interface ChannelFixture {
|
|
123
|
+
idColumn: string;
|
|
124
|
+
relationColumn: string;
|
|
125
|
+
label: string;
|
|
126
|
+
service: { findOneById: (...args: Array<never>) => unknown };
|
|
127
|
+
relationProperty: string;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const CHANNELS: Array<ChannelFixture> = [
|
|
131
|
+
{
|
|
132
|
+
idColumn: "userEmailId",
|
|
133
|
+
relationColumn: "userEmail",
|
|
134
|
+
label: "Email",
|
|
135
|
+
service: UserEmailService as unknown as ChannelFixture["service"],
|
|
136
|
+
relationProperty: "userEmail",
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
idColumn: "userSmsId",
|
|
140
|
+
relationColumn: "userSms",
|
|
141
|
+
label: "SMS",
|
|
142
|
+
service: UserSmsService as unknown as ChannelFixture["service"],
|
|
143
|
+
relationProperty: "userSms",
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
idColumn: "userCallId",
|
|
147
|
+
relationColumn: "userCall",
|
|
148
|
+
label: "Call",
|
|
149
|
+
service: UserCallService as unknown as ChannelFixture["service"],
|
|
150
|
+
relationProperty: "userCall",
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
idColumn: "userWhatsAppId",
|
|
154
|
+
relationColumn: "userWhatsApp",
|
|
155
|
+
label: "WhatsApp",
|
|
156
|
+
service: UserWhatsAppService as unknown as ChannelFixture["service"],
|
|
157
|
+
relationProperty: "userWhatsApp",
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
idColumn: "userTelegramId",
|
|
161
|
+
relationColumn: "userTelegram",
|
|
162
|
+
label: "Telegram",
|
|
163
|
+
service: UserTelegramService as unknown as ChannelFixture["service"],
|
|
164
|
+
relationProperty: "userTelegram",
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
idColumn: "userPushId",
|
|
168
|
+
relationColumn: "userPush",
|
|
169
|
+
label: "Push",
|
|
170
|
+
service: UserPushService as unknown as ChannelFixture["service"],
|
|
171
|
+
relationProperty: "userPush",
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
idColumn: "userWebhookId",
|
|
175
|
+
relationColumn: "userWebhook",
|
|
176
|
+
label: "Webhook",
|
|
177
|
+
service: UserWebhookService as unknown as ChannelFixture["service"],
|
|
178
|
+
relationProperty: "userWebhook",
|
|
179
|
+
},
|
|
180
|
+
];
|
|
181
|
+
|
|
182
|
+
/*
|
|
183
|
+
* The four hooks under test are protected. They are ordinary prototype methods
|
|
184
|
+
* at runtime; TypeScript just will not let a test name them, so they are
|
|
185
|
+
* reached through a structural cast.
|
|
186
|
+
*/
|
|
187
|
+
interface RuleServiceHooks {
|
|
188
|
+
onBeforeCreate: (
|
|
189
|
+
createBy: CreateBy<UserNotificationRule>,
|
|
190
|
+
) => Promise<OnCreate<UserNotificationRule>>;
|
|
191
|
+
onCreateSuccess: (
|
|
192
|
+
onCreate: OnCreate<UserNotificationRule>,
|
|
193
|
+
createdItem: UserNotificationRule,
|
|
194
|
+
) => Promise<UserNotificationRule>;
|
|
195
|
+
onBeforeUpdate: (
|
|
196
|
+
updateBy: UpdateBy<UserNotificationRule>,
|
|
197
|
+
) => Promise<OnUpdate<UserNotificationRule>>;
|
|
198
|
+
onUpdateSuccess: (
|
|
199
|
+
onUpdate: OnUpdate<UserNotificationRule>,
|
|
200
|
+
updatedItemIds: Array<ObjectID>,
|
|
201
|
+
) => Promise<OnUpdate<UserNotificationRule>>;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function hooks(): RuleServiceHooks {
|
|
205
|
+
return UserNotificationRuleService as unknown as RuleServiceHooks;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
interface DeliveryHalf {
|
|
209
|
+
deliverNotificationForRule: (
|
|
210
|
+
notificationRuleItem: UserNotificationRule,
|
|
211
|
+
options: ExecuteNotificationRuleOptions,
|
|
212
|
+
) => Promise<boolean>;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function deliveryHalf(): DeliveryHalf {
|
|
216
|
+
return UserNotificationRuleService as unknown as DeliveryHalf;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function memberProps(
|
|
220
|
+
overrides: Partial<DatabaseCommonInteractionProps> = {},
|
|
221
|
+
): DatabaseCommonInteractionProps {
|
|
222
|
+
return {
|
|
223
|
+
userId: ADMIN_USER_ID,
|
|
224
|
+
tenantId: PROJECT_ID,
|
|
225
|
+
...overrides,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/*
|
|
230
|
+
* A create payload as the API hands it to the hook: a real model instance,
|
|
231
|
+
* because the guard reads relation slots as well as FK columns and an object
|
|
232
|
+
* literal would not carry the model's own accessors.
|
|
233
|
+
*/
|
|
234
|
+
function createBy(data: {
|
|
235
|
+
userId?: ObjectID | undefined;
|
|
236
|
+
props?: DatabaseCommonInteractionProps | undefined;
|
|
237
|
+
channels?: Record<string, unknown> | undefined;
|
|
238
|
+
isOptOut?: boolean | undefined;
|
|
239
|
+
}): CreateBy<UserNotificationRule> {
|
|
240
|
+
const rule: UserNotificationRule = new UserNotificationRule();
|
|
241
|
+
rule.projectId = PROJECT_ID;
|
|
242
|
+
rule.ruleType = NotificationRuleType.ON_CALL_EXECUTED_INCIDENT;
|
|
243
|
+
rule.notifyAfterMinutes = 0;
|
|
244
|
+
|
|
245
|
+
if (data.userId) {
|
|
246
|
+
rule.userId = data.userId;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (data.isOptOut) {
|
|
250
|
+
rule.isOptOut = true;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
Object.assign(rule, data.channels || {});
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
data: rule,
|
|
257
|
+
props: data.props || memberProps(),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function persistedRule(
|
|
262
|
+
overrides: Record<string, unknown> = {},
|
|
263
|
+
): UserNotificationRule {
|
|
264
|
+
return {
|
|
265
|
+
id: RULE_ID,
|
|
266
|
+
_id: RULE_ID.toString(),
|
|
267
|
+
projectId: PROJECT_ID,
|
|
268
|
+
userId: VICTIM_USER_ID,
|
|
269
|
+
...overrides,
|
|
270
|
+
} as unknown as UserNotificationRule;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/*
|
|
274
|
+
* The owner notification is fire-and-forget by design - recordAdminRuleChange
|
|
275
|
+
* hands it a promise with a .catch and does not wait, so that a slow or broken
|
|
276
|
+
* mail server can never surface as a failed write. That makes "was the mail
|
|
277
|
+
* sent" a question about a promise chain the caller deliberately did not await,
|
|
278
|
+
* so the assertions have to let the microtask queue drain first. A macrotask
|
|
279
|
+
* boundary is the only reliable way to do that.
|
|
280
|
+
*/
|
|
281
|
+
function flushAsync(): Promise<void> {
|
|
282
|
+
return new Promise<void>((resolve: () => void): void => {
|
|
283
|
+
setTimeout(resolve, 0);
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/* A method row as its service hands it back: id plus the owner column. */
|
|
288
|
+
function methodOwnedBy(ownerUserId: ObjectID): unknown {
|
|
289
|
+
return {
|
|
290
|
+
id: METHOD_ID,
|
|
291
|
+
_id: METHOD_ID.toString(),
|
|
292
|
+
userId: ownerUserId,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function executeOptions(): ExecuteNotificationRuleOptions {
|
|
297
|
+
return {
|
|
298
|
+
projectId: PROJECT_ID,
|
|
299
|
+
userNotificationEventType: UserNotificationEventType.IncidentCreated,
|
|
300
|
+
triggeredByIncidentId: INCIDENT_ID,
|
|
301
|
+
onCallPolicyId: undefined,
|
|
302
|
+
userNotificationLogId: LOG_ID,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
interface Stubs {
|
|
307
|
+
teamMemberFind: jest.SpyInstance;
|
|
308
|
+
ruleFind: jest.SpyInstance;
|
|
309
|
+
ruleFindOneById: jest.SpyInstance;
|
|
310
|
+
deliver: jest.SpyInstance;
|
|
311
|
+
claim: jest.SpyInstance;
|
|
312
|
+
timelineCreate: jest.SpyInstance;
|
|
313
|
+
mail: jest.SpyInstance;
|
|
314
|
+
userFind: jest.SpyInstance;
|
|
315
|
+
projectFind: jest.SpyInstance;
|
|
316
|
+
dashboardUrl: jest.SpyInstance;
|
|
317
|
+
auditCreate: jest.SpyInstance;
|
|
318
|
+
auditUpdate: jest.SpyInstance;
|
|
319
|
+
methodFinds: Map<string, jest.SpyInstance>;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
describe("UserNotificationRule administrative write guards", () => {
|
|
323
|
+
let stubs: Stubs;
|
|
324
|
+
let timelineRows: Array<UserOnCallLogTimeline>;
|
|
325
|
+
|
|
326
|
+
beforeEach(() => {
|
|
327
|
+
timelineRows = [];
|
|
328
|
+
|
|
329
|
+
jest.spyOn(logger, "error").mockImplementation((): void => {
|
|
330
|
+
return undefined;
|
|
331
|
+
});
|
|
332
|
+
jest.spyOn(logger, "info").mockImplementation((): void => {
|
|
333
|
+
return undefined;
|
|
334
|
+
});
|
|
335
|
+
jest.spyOn(logger, "warn").mockImplementation((): void => {
|
|
336
|
+
return undefined;
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
const methodFinds: Map<string, jest.SpyInstance> = new Map<
|
|
340
|
+
string,
|
|
341
|
+
jest.SpyInstance
|
|
342
|
+
>();
|
|
343
|
+
|
|
344
|
+
for (const channel of CHANNELS) {
|
|
345
|
+
/*
|
|
346
|
+
* Default: every method row belongs to the victim. Tests that care make
|
|
347
|
+
* the one channel they are about disagree, so a guard that consults the
|
|
348
|
+
* WRONG service still fails the test rather than accidentally passing.
|
|
349
|
+
*/
|
|
350
|
+
methodFinds.set(
|
|
351
|
+
channel.idColumn,
|
|
352
|
+
jest
|
|
353
|
+
.spyOn(channel.service, "findOneById")
|
|
354
|
+
.mockResolvedValue(methodOwnedBy(VICTIM_USER_ID) as never),
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
stubs = {
|
|
359
|
+
teamMemberFind: jest
|
|
360
|
+
.spyOn(TeamMemberService, "findOneBy")
|
|
361
|
+
.mockResolvedValue({
|
|
362
|
+
id: TEAM_MEMBER_ID,
|
|
363
|
+
_id: TEAM_MEMBER_ID.toString(),
|
|
364
|
+
} as unknown as TeamMember as never),
|
|
365
|
+
ruleFind: jest
|
|
366
|
+
.spyOn(UserNotificationRuleService, "findBy")
|
|
367
|
+
.mockResolvedValue([persistedRule()] as never),
|
|
368
|
+
ruleFindOneById: jest
|
|
369
|
+
.spyOn(UserNotificationRuleService, "findOneById")
|
|
370
|
+
.mockResolvedValue(null as never),
|
|
371
|
+
deliver: jest
|
|
372
|
+
.spyOn(deliveryHalf(), "deliverNotificationForRule")
|
|
373
|
+
.mockResolvedValue(true as never),
|
|
374
|
+
claim: jest
|
|
375
|
+
.spyOn(UserOnCallLogService, "claimNotificationRuleExecution")
|
|
376
|
+
.mockResolvedValue(true as never),
|
|
377
|
+
timelineCreate: jest
|
|
378
|
+
.spyOn(UserOnCallLogTimelineService, "create")
|
|
379
|
+
.mockImplementation(
|
|
380
|
+
(
|
|
381
|
+
createByArg: CreateBy<UserOnCallLogTimeline>,
|
|
382
|
+
): Promise<UserOnCallLogTimeline> => {
|
|
383
|
+
timelineRows.push(createByArg.data);
|
|
384
|
+
|
|
385
|
+
return Promise.resolve({
|
|
386
|
+
id: TIMELINE_ID,
|
|
387
|
+
} as unknown as UserOnCallLogTimeline);
|
|
388
|
+
},
|
|
389
|
+
) as unknown as jest.SpyInstance,
|
|
390
|
+
mail: jest
|
|
391
|
+
.spyOn(MailService, "sendMail")
|
|
392
|
+
.mockResolvedValue(undefined as never),
|
|
393
|
+
userFind: jest
|
|
394
|
+
.spyOn(UserService, "findOneById")
|
|
395
|
+
.mockImplementation(
|
|
396
|
+
(findBy: FindOneByID<User>): Promise<User | null> => {
|
|
397
|
+
const id: ObjectID = findBy.id;
|
|
398
|
+
|
|
399
|
+
return Promise.resolve({
|
|
400
|
+
id: id,
|
|
401
|
+
_id: id.toString(),
|
|
402
|
+
name: new Name(
|
|
403
|
+
id.toString() === ADMIN_USER_ID.toString()
|
|
404
|
+
? "Ada Admin"
|
|
405
|
+
: "Vic",
|
|
406
|
+
),
|
|
407
|
+
email: new Email(
|
|
408
|
+
id.toString() === ADMIN_USER_ID.toString()
|
|
409
|
+
? "ada@example.com"
|
|
410
|
+
: "vic@example.com",
|
|
411
|
+
),
|
|
412
|
+
} as unknown as User);
|
|
413
|
+
},
|
|
414
|
+
) as unknown as jest.SpyInstance,
|
|
415
|
+
projectFind: jest.spyOn(ProjectService, "findOneById").mockResolvedValue({
|
|
416
|
+
id: PROJECT_ID,
|
|
417
|
+
name: "Acme",
|
|
418
|
+
} as unknown as Project as never),
|
|
419
|
+
dashboardUrl: jest
|
|
420
|
+
.spyOn(DatabaseConfig, "getDashboardUrl")
|
|
421
|
+
.mockResolvedValue(URL.fromString("https://oneuptime.test") as never),
|
|
422
|
+
auditCreate: jest
|
|
423
|
+
.spyOn(AuditLogService, "recordCreate")
|
|
424
|
+
.mockResolvedValue(undefined as never),
|
|
425
|
+
auditUpdate: jest
|
|
426
|
+
.spyOn(AuditLogService, "recordUpdate")
|
|
427
|
+
.mockResolvedValue(undefined as never),
|
|
428
|
+
methodFinds: methodFinds,
|
|
429
|
+
};
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
afterEach(() => {
|
|
433
|
+
jest.restoreAllMocks();
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
/*
|
|
437
|
+
* ---------------------------------------------------------------------
|
|
438
|
+
* R1 - the target of an on-behalf-of write must be in THIS project.
|
|
439
|
+
* ---------------------------------------------------------------------
|
|
440
|
+
*/
|
|
441
|
+
describe("R1 - the rule's owner must be a member of the acting project", () => {
|
|
442
|
+
test("a user id from another project is refused even though the caller is a real admin here", async () => {
|
|
443
|
+
/*
|
|
444
|
+
* The whole attack in one call. The caller genuinely holds an
|
|
445
|
+
* administrative permission on PROJECT_ID, so the permission layer waves
|
|
446
|
+
* them through; the id they name belongs to a user of a project they have
|
|
447
|
+
* nothing to do with. Only a roster check can tell the two apart, and the
|
|
448
|
+
* roster query is what returns nothing here.
|
|
449
|
+
*/
|
|
450
|
+
stubs.teamMemberFind.mockResolvedValue(null as never);
|
|
451
|
+
|
|
452
|
+
await expect(
|
|
453
|
+
hooks().onBeforeCreate(
|
|
454
|
+
createBy({
|
|
455
|
+
userId: OUTSIDER_USER_ID,
|
|
456
|
+
channels: { userEmailId: METHOD_ID },
|
|
457
|
+
}),
|
|
458
|
+
),
|
|
459
|
+
).rejects.toThrow(
|
|
460
|
+
`Cannot create a notification rule for user ${OUTSIDER_USER_ID.toString()} because they are not a member of this project.`,
|
|
461
|
+
);
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
test("the roster is queried for the acting tenant, with root props and an accepted invitation", async () => {
|
|
465
|
+
await hooks().onBeforeCreate(
|
|
466
|
+
createBy({
|
|
467
|
+
userId: VICTIM_USER_ID,
|
|
468
|
+
channels: { userEmailId: METHOD_ID },
|
|
469
|
+
}),
|
|
470
|
+
);
|
|
471
|
+
|
|
472
|
+
const query: {
|
|
473
|
+
query: {
|
|
474
|
+
userId: ObjectID;
|
|
475
|
+
projectId: ObjectID;
|
|
476
|
+
hasAcceptedInvitation: boolean;
|
|
477
|
+
};
|
|
478
|
+
props: { isRoot: boolean };
|
|
479
|
+
} = stubs.teamMemberFind.mock.calls[0]![0] as {
|
|
480
|
+
query: {
|
|
481
|
+
userId: ObjectID;
|
|
482
|
+
projectId: ObjectID;
|
|
483
|
+
hasAcceptedInvitation: boolean;
|
|
484
|
+
};
|
|
485
|
+
props: { isRoot: boolean };
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
expect(query.query.userId.toString()).toBe(VICTIM_USER_ID.toString());
|
|
489
|
+
/*
|
|
490
|
+
* props.tenantId, NOT a projectId out of the body. A body-supplied
|
|
491
|
+
* project is the attacker naming the roster they want to be checked
|
|
492
|
+
* against, which is the same as no check.
|
|
493
|
+
*/
|
|
494
|
+
expect(query.query.projectId.toString()).toBe(PROJECT_ID.toString());
|
|
495
|
+
expect(query.query.hasAcceptedInvitation).toBe(true);
|
|
496
|
+
/*
|
|
497
|
+
* isRoot, because the question is about the database's state. Asking it
|
|
498
|
+
* through the caller's own scope would let a caller who cannot READ the
|
|
499
|
+
* roster get "no rows" back and, under any lenient reading, pass.
|
|
500
|
+
*/
|
|
501
|
+
expect(query.props.isRoot).toBe(true);
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
test("a request with no tenant scope cannot write for another user at all", async () => {
|
|
505
|
+
await expect(
|
|
506
|
+
hooks().onBeforeCreate(
|
|
507
|
+
createBy({
|
|
508
|
+
userId: VICTIM_USER_ID,
|
|
509
|
+
props: memberProps({ tenantId: undefined }),
|
|
510
|
+
channels: { userEmailId: METHOD_ID },
|
|
511
|
+
}),
|
|
512
|
+
),
|
|
513
|
+
).rejects.toThrow(
|
|
514
|
+
"A project is required to create a notification rule for another user.",
|
|
515
|
+
);
|
|
516
|
+
|
|
517
|
+
expect(stubs.teamMemberFind).not.toHaveBeenCalled();
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
test("a tenant id from a DIFFERENT project scopes the roster query to that project, not to the rule's projectId", async () => {
|
|
521
|
+
/*
|
|
522
|
+
* The rule body says projectId = PROJECT_ID while the session is scoped
|
|
523
|
+
* to OTHER_PROJECT_ID. Membership must be judged by the session, so the
|
|
524
|
+
* query has to name OTHER_PROJECT_ID.
|
|
525
|
+
*/
|
|
526
|
+
stubs.teamMemberFind.mockResolvedValue(null as never);
|
|
527
|
+
|
|
528
|
+
await expect(
|
|
529
|
+
hooks().onBeforeCreate(
|
|
530
|
+
createBy({
|
|
531
|
+
userId: VICTIM_USER_ID,
|
|
532
|
+
props: memberProps({ tenantId: OTHER_PROJECT_ID }),
|
|
533
|
+
channels: { userEmailId: METHOD_ID },
|
|
534
|
+
}),
|
|
535
|
+
),
|
|
536
|
+
).rejects.toThrow("are not a member of this project");
|
|
537
|
+
|
|
538
|
+
const query: { query: { projectId: ObjectID } } = stubs.teamMemberFind
|
|
539
|
+
.mock.calls[0]![0] as { query: { projectId: ObjectID } };
|
|
540
|
+
|
|
541
|
+
expect(query.query.projectId.toString()).toBe(
|
|
542
|
+
OTHER_PROJECT_ID.toString(),
|
|
543
|
+
);
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
test("a self-write does not consult the roster - a member configuring themselves is not an admin action", async () => {
|
|
547
|
+
stubs.methodFinds
|
|
548
|
+
.get("userEmailId")!
|
|
549
|
+
.mockResolvedValue(methodOwnedBy(ADMIN_USER_ID) as never);
|
|
550
|
+
|
|
551
|
+
await hooks().onBeforeCreate(
|
|
552
|
+
createBy({
|
|
553
|
+
userId: ADMIN_USER_ID,
|
|
554
|
+
channels: { userEmailId: METHOD_ID },
|
|
555
|
+
}),
|
|
556
|
+
);
|
|
557
|
+
|
|
558
|
+
expect(stubs.teamMemberFind).not.toHaveBeenCalled();
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
test("an omitted ownership column is a self-write, because CreatePermission stamps it after this hook runs", async () => {
|
|
562
|
+
/*
|
|
563
|
+
* Reading data.userId alone here would see `undefined` on every ordinary
|
|
564
|
+
* self-service create - the value is stamped later - and would send every
|
|
565
|
+
* one of them through the on-behalf-of branch.
|
|
566
|
+
*/
|
|
567
|
+
stubs.methodFinds
|
|
568
|
+
.get("userEmailId")!
|
|
569
|
+
.mockResolvedValue(methodOwnedBy(ADMIN_USER_ID) as never);
|
|
570
|
+
|
|
571
|
+
await hooks().onBeforeCreate(
|
|
572
|
+
createBy({
|
|
573
|
+
channels: { userEmailId: METHOD_ID },
|
|
574
|
+
}),
|
|
575
|
+
);
|
|
576
|
+
|
|
577
|
+
expect(stubs.teamMemberFind).not.toHaveBeenCalled();
|
|
578
|
+
/*
|
|
579
|
+
* ...but the method it names is still checked, against the actor as the
|
|
580
|
+
* implied owner.
|
|
581
|
+
*/
|
|
582
|
+
expect(stubs.methodFinds.get("userEmailId")).toHaveBeenCalled();
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
test("an opt-out row for another user is still roster-checked - it has no method, but it still silences somebody", async () => {
|
|
586
|
+
stubs.teamMemberFind.mockResolvedValue(null as never);
|
|
587
|
+
|
|
588
|
+
await expect(
|
|
589
|
+
hooks().onBeforeCreate(
|
|
590
|
+
createBy({
|
|
591
|
+
userId: OUTSIDER_USER_ID,
|
|
592
|
+
isOptOut: true,
|
|
593
|
+
}),
|
|
594
|
+
),
|
|
595
|
+
).rejects.toThrow("are not a member of this project");
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
test("an actor-less caller writing for a named user is treated as on-behalf-of, not as a self-write", async () => {
|
|
599
|
+
/*
|
|
600
|
+
* An API key has no user identity. If the widened create list ever admits
|
|
601
|
+
* one, "actor equals owner" is trivially false and must NOT be read as
|
|
602
|
+
* "nobody else involved, carry on".
|
|
603
|
+
*/
|
|
604
|
+
stubs.teamMemberFind.mockResolvedValue(null as never);
|
|
605
|
+
|
|
606
|
+
await expect(
|
|
607
|
+
hooks().onBeforeCreate(
|
|
608
|
+
createBy({
|
|
609
|
+
userId: VICTIM_USER_ID,
|
|
610
|
+
props: { tenantId: PROJECT_ID },
|
|
611
|
+
channels: { userEmailId: METHOD_ID },
|
|
612
|
+
}),
|
|
613
|
+
),
|
|
614
|
+
).rejects.toThrow("are not a member of this project");
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
test("a rule with neither an owner nor an actor is refused rather than written unowned", async () => {
|
|
618
|
+
await expect(
|
|
619
|
+
hooks().onBeforeCreate(
|
|
620
|
+
createBy({
|
|
621
|
+
props: { tenantId: PROJECT_ID },
|
|
622
|
+
channels: { userEmailId: METHOD_ID },
|
|
623
|
+
}),
|
|
624
|
+
),
|
|
625
|
+
).rejects.toThrow("A notification rule must belong to a user.");
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
test("root writes skip both guards - internal seeding builds both halves from one userId", async () => {
|
|
629
|
+
await hooks().onBeforeCreate(
|
|
630
|
+
createBy({
|
|
631
|
+
userId: VICTIM_USER_ID,
|
|
632
|
+
props: { isRoot: true },
|
|
633
|
+
channels: { userEmailId: METHOD_ID },
|
|
634
|
+
}),
|
|
635
|
+
);
|
|
636
|
+
|
|
637
|
+
expect(stubs.teamMemberFind).not.toHaveBeenCalled();
|
|
638
|
+
expect(stubs.methodFinds.get("userEmailId")).not.toHaveBeenCalled();
|
|
639
|
+
});
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
/*
|
|
643
|
+
* ---------------------------------------------------------------------
|
|
644
|
+
* R3 on create - the method a rule names must belong to the rule's owner.
|
|
645
|
+
* ---------------------------------------------------------------------
|
|
646
|
+
*/
|
|
647
|
+
describe("R3 on create - method ownership, all seven channels", () => {
|
|
648
|
+
test.each(
|
|
649
|
+
CHANNELS.map((channel: ChannelFixture): [string, ChannelFixture] => {
|
|
650
|
+
return [channel.label, channel];
|
|
651
|
+
}),
|
|
652
|
+
)(
|
|
653
|
+
"%s: a rule for the victim may not point at a method owned by the admin",
|
|
654
|
+
async (_label: string, channel: ChannelFixture) => {
|
|
655
|
+
/*
|
|
656
|
+
* THE hijack. userId names the victim so the victim's pages select the
|
|
657
|
+
* row; the method FK names something the admin controls so the pages
|
|
658
|
+
* arrive there instead.
|
|
659
|
+
*/
|
|
660
|
+
stubs.methodFinds
|
|
661
|
+
.get(channel.idColumn)!
|
|
662
|
+
.mockResolvedValue(methodOwnedBy(ADMIN_USER_ID) as never);
|
|
663
|
+
|
|
664
|
+
await expect(
|
|
665
|
+
hooks().onBeforeCreate(
|
|
666
|
+
createBy({
|
|
667
|
+
userId: VICTIM_USER_ID,
|
|
668
|
+
channels: { [channel.idColumn]: METHOD_ID },
|
|
669
|
+
}),
|
|
670
|
+
),
|
|
671
|
+
).rejects.toThrow(
|
|
672
|
+
`The ${channel.label} notification method referenced by this rule belongs to a different user.`,
|
|
673
|
+
);
|
|
674
|
+
},
|
|
675
|
+
);
|
|
676
|
+
|
|
677
|
+
test.each(
|
|
678
|
+
CHANNELS.map((channel: ChannelFixture): [string, ChannelFixture] => {
|
|
679
|
+
return [channel.label, channel];
|
|
680
|
+
}),
|
|
681
|
+
)(
|
|
682
|
+
"%s: the same rule is accepted when the method belongs to the rule's owner",
|
|
683
|
+
async (_label: string, channel: ChannelFixture) => {
|
|
684
|
+
await expect(
|
|
685
|
+
hooks().onBeforeCreate(
|
|
686
|
+
createBy({
|
|
687
|
+
userId: VICTIM_USER_ID,
|
|
688
|
+
channels: { [channel.idColumn]: METHOD_ID },
|
|
689
|
+
}),
|
|
690
|
+
),
|
|
691
|
+
).resolves.toBeDefined();
|
|
692
|
+
|
|
693
|
+
expect(stubs.methodFinds.get(channel.idColumn)).toHaveBeenCalled();
|
|
694
|
+
},
|
|
695
|
+
);
|
|
696
|
+
|
|
697
|
+
test.each(
|
|
698
|
+
CHANNELS.map((channel: ChannelFixture): [string, ChannelFixture] => {
|
|
699
|
+
return [channel.label, channel];
|
|
700
|
+
}),
|
|
701
|
+
)(
|
|
702
|
+
"%s: the relation slot is the same write spelled differently and is caught too",
|
|
703
|
+
async (_label: string, channel: ChannelFixture) => {
|
|
704
|
+
stubs.methodFinds
|
|
705
|
+
.get(channel.idColumn)!
|
|
706
|
+
.mockResolvedValue(methodOwnedBy(ADMIN_USER_ID) as never);
|
|
707
|
+
|
|
708
|
+
await expect(
|
|
709
|
+
hooks().onBeforeCreate(
|
|
710
|
+
createBy({
|
|
711
|
+
userId: VICTIM_USER_ID,
|
|
712
|
+
channels: {
|
|
713
|
+
[channel.relationColumn]: { _id: METHOD_ID.toString() },
|
|
714
|
+
},
|
|
715
|
+
}),
|
|
716
|
+
),
|
|
717
|
+
).rejects.toThrow(
|
|
718
|
+
`The ${channel.label} notification method referenced by this rule belongs to a different user.`,
|
|
719
|
+
);
|
|
720
|
+
},
|
|
721
|
+
);
|
|
722
|
+
|
|
723
|
+
test("the guard covers exactly the seven notification method columns the model has", () => {
|
|
724
|
+
/*
|
|
725
|
+
* The count is the assertion. An eighth channel added to the model
|
|
726
|
+
* without an entry in the guard's table is a channel with no ownership
|
|
727
|
+
* check at all, and the attacker only needs one.
|
|
728
|
+
*/
|
|
729
|
+
const covered: Array<string> =
|
|
730
|
+
UserNotificationRuleAdminService.getNotificationMethodIdColumns();
|
|
731
|
+
|
|
732
|
+
expect(covered.sort()).toEqual(
|
|
733
|
+
CHANNELS.map((channel: ChannelFixture): string => {
|
|
734
|
+
return channel.idColumn;
|
|
735
|
+
}).sort(),
|
|
736
|
+
);
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
test("a method row that does not exist is refused, not ignored", async () => {
|
|
740
|
+
/*
|
|
741
|
+
* "Not found" and "not yours" are the same answer: in both cases the
|
|
742
|
+
* caller named a row they have no business naming. A guard that compares
|
|
743
|
+
* `undefined` against `undefined` opens on exactly the input it exists to
|
|
744
|
+
* reject.
|
|
745
|
+
*/
|
|
746
|
+
stubs.methodFinds.get("userEmailId")!.mockResolvedValue(null as never);
|
|
747
|
+
|
|
748
|
+
await expect(
|
|
749
|
+
hooks().onBeforeCreate(
|
|
750
|
+
createBy({
|
|
751
|
+
userId: VICTIM_USER_ID,
|
|
752
|
+
channels: { userEmailId: METHOD_ID },
|
|
753
|
+
}),
|
|
754
|
+
),
|
|
755
|
+
).rejects.toThrow(
|
|
756
|
+
"The Email notification method referenced by this rule does not exist.",
|
|
757
|
+
);
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
test("a method row with no owner at all is refused", async () => {
|
|
761
|
+
stubs.methodFinds
|
|
762
|
+
.get("userEmailId")!
|
|
763
|
+
.mockResolvedValue({ id: METHOD_ID } as never);
|
|
764
|
+
|
|
765
|
+
await expect(
|
|
766
|
+
hooks().onBeforeCreate(
|
|
767
|
+
createBy({
|
|
768
|
+
userId: VICTIM_USER_ID,
|
|
769
|
+
channels: { userEmailId: METHOD_ID },
|
|
770
|
+
}),
|
|
771
|
+
),
|
|
772
|
+
).rejects.toThrow(
|
|
773
|
+
"The Email notification method referenced by this rule does not exist.",
|
|
774
|
+
);
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
test("the mirror hijack is refused too: my own rule may not point at your address", async () => {
|
|
778
|
+
/*
|
|
779
|
+
* userId = me, userEmailId = yours. This does not steal my pages, it
|
|
780
|
+
* COPIES them to you - and it needed no widened permission, so it was
|
|
781
|
+
* writable long before this phase. The guard runs on self-writes for
|
|
782
|
+
* exactly this reason.
|
|
783
|
+
*/
|
|
784
|
+
stubs.methodFinds
|
|
785
|
+
.get("userEmailId")!
|
|
786
|
+
.mockResolvedValue(methodOwnedBy(VICTIM_USER_ID) as never);
|
|
787
|
+
|
|
788
|
+
await expect(
|
|
789
|
+
hooks().onBeforeCreate(
|
|
790
|
+
createBy({
|
|
791
|
+
userId: ADMIN_USER_ID,
|
|
792
|
+
channels: { userEmailId: METHOD_ID },
|
|
793
|
+
}),
|
|
794
|
+
),
|
|
795
|
+
).rejects.toThrow(
|
|
796
|
+
"The Email notification method referenced by this rule belongs to a different user.",
|
|
797
|
+
);
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
test("the method's owner is read with root props, so the caller's own scope cannot shape the answer", async () => {
|
|
801
|
+
await hooks().onBeforeCreate(
|
|
802
|
+
createBy({
|
|
803
|
+
userId: VICTIM_USER_ID,
|
|
804
|
+
channels: { userEmailId: METHOD_ID },
|
|
805
|
+
}),
|
|
806
|
+
);
|
|
807
|
+
|
|
808
|
+
const call: { id: ObjectID; props: { isRoot: boolean } } =
|
|
809
|
+
stubs.methodFinds.get("userEmailId")!.mock.calls[0]![0] as {
|
|
810
|
+
id: ObjectID;
|
|
811
|
+
props: { isRoot: boolean };
|
|
812
|
+
};
|
|
813
|
+
|
|
814
|
+
expect(call.id.toString()).toBe(METHOD_ID.toString());
|
|
815
|
+
expect(call.props.isRoot).toBe(true);
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
test("a rule naming several methods is refused when any one of them is foreign", async () => {
|
|
819
|
+
stubs.methodFinds
|
|
820
|
+
.get("userWebhookId")!
|
|
821
|
+
.mockResolvedValue(methodOwnedBy(ADMIN_USER_ID) as never);
|
|
822
|
+
|
|
823
|
+
await expect(
|
|
824
|
+
hooks().onBeforeCreate(
|
|
825
|
+
createBy({
|
|
826
|
+
userId: VICTIM_USER_ID,
|
|
827
|
+
channels: {
|
|
828
|
+
userEmailId: METHOD_ID,
|
|
829
|
+
userWebhookId: METHOD_ID,
|
|
830
|
+
},
|
|
831
|
+
}),
|
|
832
|
+
),
|
|
833
|
+
).rejects.toThrow("Webhook notification method");
|
|
834
|
+
});
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
/*
|
|
838
|
+
* ---------------------------------------------------------------------
|
|
839
|
+
* R3 on update - the owner is re-read from the database, never believed.
|
|
840
|
+
* ---------------------------------------------------------------------
|
|
841
|
+
*/
|
|
842
|
+
describe("R3 on update - the rule's owner comes from the database", () => {
|
|
843
|
+
test("a body that claims the rule is the caller's own does not stop the guard", async () => {
|
|
844
|
+
/*
|
|
845
|
+
* The highest-value assertion in this file. The attacker sends
|
|
846
|
+
* `{ userId: <me>, userWebhookId: <my webhook> }` against a row that
|
|
847
|
+
* actually belongs to the victim. Believing the body would make the two
|
|
848
|
+
* halves agree and the guard would pass; re-reading the row makes them
|
|
849
|
+
* disagree and it fails.
|
|
850
|
+
*/
|
|
851
|
+
stubs.ruleFind.mockResolvedValue([
|
|
852
|
+
persistedRule({ userId: VICTIM_USER_ID }),
|
|
853
|
+
] as never);
|
|
854
|
+
stubs.methodFinds
|
|
855
|
+
.get("userWebhookId")!
|
|
856
|
+
.mockResolvedValue(methodOwnedBy(ADMIN_USER_ID) as never);
|
|
857
|
+
|
|
858
|
+
await expect(
|
|
859
|
+
hooks().onBeforeUpdate({
|
|
860
|
+
query: { _id: RULE_ID.toString() },
|
|
861
|
+
data: {
|
|
862
|
+
userId: ADMIN_USER_ID,
|
|
863
|
+
userWebhookId: METHOD_ID,
|
|
864
|
+
},
|
|
865
|
+
limit: 1,
|
|
866
|
+
skip: 0,
|
|
867
|
+
props: memberProps(),
|
|
868
|
+
} as unknown as UpdateBy<UserNotificationRule>),
|
|
869
|
+
).rejects.toThrow(
|
|
870
|
+
"The Webhook notification method referenced by this rule belongs to a different user.",
|
|
871
|
+
);
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
test.each(
|
|
875
|
+
CHANNELS.map((channel: ChannelFixture): [string, ChannelFixture] => {
|
|
876
|
+
return [channel.label, channel];
|
|
877
|
+
}),
|
|
878
|
+
)(
|
|
879
|
+
"%s: repointing a victim's rule at the admin's own method is refused",
|
|
880
|
+
async (_label: string, channel: ChannelFixture) => {
|
|
881
|
+
stubs.methodFinds
|
|
882
|
+
.get(channel.idColumn)!
|
|
883
|
+
.mockResolvedValue(methodOwnedBy(ADMIN_USER_ID) as never);
|
|
884
|
+
|
|
885
|
+
await expect(
|
|
886
|
+
hooks().onBeforeUpdate({
|
|
887
|
+
query: { _id: RULE_ID.toString() },
|
|
888
|
+
data: { [channel.idColumn]: METHOD_ID },
|
|
889
|
+
limit: 1,
|
|
890
|
+
skip: 0,
|
|
891
|
+
props: memberProps(),
|
|
892
|
+
} as unknown as UpdateBy<UserNotificationRule>),
|
|
893
|
+
).rejects.toThrow(
|
|
894
|
+
`The ${channel.label} notification method referenced by this rule belongs to a different user.`,
|
|
895
|
+
);
|
|
896
|
+
},
|
|
897
|
+
);
|
|
898
|
+
|
|
899
|
+
test("repointing at a method the rule's owner actually owns is allowed - admins must be able to repair", async () => {
|
|
900
|
+
await expect(
|
|
901
|
+
hooks().onBeforeUpdate({
|
|
902
|
+
query: { _id: RULE_ID.toString() },
|
|
903
|
+
data: { userEmailId: METHOD_ID },
|
|
904
|
+
limit: 1,
|
|
905
|
+
skip: 0,
|
|
906
|
+
props: memberProps(),
|
|
907
|
+
} as unknown as UpdateBy<UserNotificationRule>),
|
|
908
|
+
).resolves.toBeDefined();
|
|
909
|
+
});
|
|
910
|
+
|
|
911
|
+
test("the affected rows are read with root props, so a caller who cannot see a row cannot edit it unchecked", async () => {
|
|
912
|
+
await hooks().onBeforeUpdate({
|
|
913
|
+
query: { _id: RULE_ID.toString() },
|
|
914
|
+
data: { userEmailId: METHOD_ID },
|
|
915
|
+
limit: 1,
|
|
916
|
+
skip: 0,
|
|
917
|
+
props: memberProps(),
|
|
918
|
+
} as unknown as UpdateBy<UserNotificationRule>);
|
|
919
|
+
|
|
920
|
+
const call: { props: { isRoot: boolean }; select: { userId: boolean } } =
|
|
921
|
+
stubs.ruleFind.mock.calls[0]![0] as {
|
|
922
|
+
props: { isRoot: boolean };
|
|
923
|
+
select: { userId: boolean };
|
|
924
|
+
};
|
|
925
|
+
|
|
926
|
+
expect(call.props.isRoot).toBe(true);
|
|
927
|
+
expect(call.select.userId).toBe(true);
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
test("every distinct owner in a multi-row update is validated, not just the first", async () => {
|
|
931
|
+
/*
|
|
932
|
+
* One request, two rules, two different owners, one method. The method
|
|
933
|
+
* can belong to at most one of them, so the update must be refused - and
|
|
934
|
+
* it is only refused if the loop keeps going past the row that passed.
|
|
935
|
+
*/
|
|
936
|
+
stubs.ruleFind.mockResolvedValue([
|
|
937
|
+
persistedRule({ userId: VICTIM_USER_ID }),
|
|
938
|
+
persistedRule({ userId: OUTSIDER_USER_ID }),
|
|
939
|
+
] as never);
|
|
940
|
+
|
|
941
|
+
await expect(
|
|
942
|
+
hooks().onBeforeUpdate({
|
|
943
|
+
query: { ruleType: NotificationRuleType.ON_CALL_EXECUTED_INCIDENT },
|
|
944
|
+
data: { userEmailId: METHOD_ID },
|
|
945
|
+
limit: 10,
|
|
946
|
+
skip: 0,
|
|
947
|
+
props: memberProps(),
|
|
948
|
+
} as unknown as UpdateBy<UserNotificationRule>),
|
|
949
|
+
).rejects.toThrow("belongs to a different user");
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
test("an update naming no method at all skips the ownership lookups entirely", async () => {
|
|
953
|
+
await hooks().onBeforeUpdate({
|
|
954
|
+
query: { _id: RULE_ID.toString() },
|
|
955
|
+
data: { notifyAfterMinutes: 5 },
|
|
956
|
+
limit: 1,
|
|
957
|
+
skip: 0,
|
|
958
|
+
props: memberProps(),
|
|
959
|
+
} as unknown as UpdateBy<UserNotificationRule>);
|
|
960
|
+
|
|
961
|
+
for (const channel of CHANNELS) {
|
|
962
|
+
expect(stubs.methodFinds.get(channel.idColumn)).not.toHaveBeenCalled();
|
|
963
|
+
}
|
|
964
|
+
});
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
/*
|
|
968
|
+
* ---------------------------------------------------------------------
|
|
969
|
+
* R6 - audit and notify, keyed on the server's actor vs the persisted owner.
|
|
970
|
+
* ---------------------------------------------------------------------
|
|
971
|
+
*/
|
|
972
|
+
describe("R6 - audit trail and owner notification", () => {
|
|
973
|
+
test("creating a rule for somebody else audits the actor against the PERSISTED owner", async () => {
|
|
974
|
+
const onCreate: OnCreate<UserNotificationRule> = {
|
|
975
|
+
createBy: createBy({ userId: VICTIM_USER_ID }),
|
|
976
|
+
carryForward: null,
|
|
977
|
+
};
|
|
978
|
+
|
|
979
|
+
await hooks().onCreateSuccess(
|
|
980
|
+
onCreate,
|
|
981
|
+
persistedRule({ userId: VICTIM_USER_ID }),
|
|
982
|
+
);
|
|
983
|
+
|
|
984
|
+
expect(stubs.auditCreate).toHaveBeenCalledTimes(1);
|
|
985
|
+
|
|
986
|
+
const logged: string = (
|
|
987
|
+
logger.info as unknown as jest.SpyInstance
|
|
988
|
+
).mock.calls
|
|
989
|
+
.flat()
|
|
990
|
+
.map((value: unknown): string => {
|
|
991
|
+
return JSON.stringify(value);
|
|
992
|
+
})
|
|
993
|
+
.join(" ");
|
|
994
|
+
|
|
995
|
+
expect(logged).toContain(VICTIM_USER_ID.toString());
|
|
996
|
+
expect(logged).toContain(ADMIN_USER_ID.toString());
|
|
997
|
+
});
|
|
998
|
+
|
|
999
|
+
test("the owner compared against is the row that was written, not the one the body asked for", async () => {
|
|
1000
|
+
/*
|
|
1001
|
+
* The body claims the rule is the admin's own; the row that was actually
|
|
1002
|
+
* persisted belongs to the victim. Reading the body would conclude "self
|
|
1003
|
+
* write, nothing to report" and the victim would never hear that their
|
|
1004
|
+
* paging changed - which is precisely the notification an attacker most
|
|
1005
|
+
* wants suppressed.
|
|
1006
|
+
*/
|
|
1007
|
+
const onCreate: OnCreate<UserNotificationRule> = {
|
|
1008
|
+
createBy: createBy({ userId: ADMIN_USER_ID }),
|
|
1009
|
+
carryForward: null,
|
|
1010
|
+
};
|
|
1011
|
+
|
|
1012
|
+
await hooks().onCreateSuccess(
|
|
1013
|
+
onCreate,
|
|
1014
|
+
persistedRule({ userId: VICTIM_USER_ID }),
|
|
1015
|
+
);
|
|
1016
|
+
|
|
1017
|
+
await flushAsync();
|
|
1018
|
+
|
|
1019
|
+
expect(stubs.auditCreate).toHaveBeenCalledTimes(1);
|
|
1020
|
+
expect(stubs.mail).toHaveBeenCalledTimes(1);
|
|
1021
|
+
});
|
|
1022
|
+
|
|
1023
|
+
test("the owner is emailed, and the mail names the actor by address", async () => {
|
|
1024
|
+
await hooks().onCreateSuccess(
|
|
1025
|
+
{
|
|
1026
|
+
createBy: createBy({ userId: VICTIM_USER_ID }),
|
|
1027
|
+
carryForward: null,
|
|
1028
|
+
},
|
|
1029
|
+
persistedRule({ userId: VICTIM_USER_ID }),
|
|
1030
|
+
);
|
|
1031
|
+
|
|
1032
|
+
await flushAsync();
|
|
1033
|
+
|
|
1034
|
+
expect(stubs.mail).toHaveBeenCalledTimes(1);
|
|
1035
|
+
|
|
1036
|
+
const mailArg: { toEmail: Email; vars: { message: string } } = stubs.mail
|
|
1037
|
+
.mock.calls[0]![0] as { toEmail: Email; vars: { message: string } };
|
|
1038
|
+
|
|
1039
|
+
expect(mailArg.toEmail.toString()).toBe("vic@example.com");
|
|
1040
|
+
/*
|
|
1041
|
+
* Display names are user-editable and not unique, so "Ada changed your
|
|
1042
|
+
* rules" is not something the reader can act on. An address is.
|
|
1043
|
+
*/
|
|
1044
|
+
expect(mailArg.vars.message).toContain("ada@example.com");
|
|
1045
|
+
});
|
|
1046
|
+
|
|
1047
|
+
test("a self-write is neither audited nor announced", async () => {
|
|
1048
|
+
await hooks().onCreateSuccess(
|
|
1049
|
+
{
|
|
1050
|
+
createBy: createBy({ userId: ADMIN_USER_ID }),
|
|
1051
|
+
carryForward: null,
|
|
1052
|
+
},
|
|
1053
|
+
persistedRule({ userId: ADMIN_USER_ID }),
|
|
1054
|
+
);
|
|
1055
|
+
|
|
1056
|
+
await flushAsync();
|
|
1057
|
+
|
|
1058
|
+
expect(stubs.auditCreate).not.toHaveBeenCalled();
|
|
1059
|
+
expect(stubs.mail).not.toHaveBeenCalled();
|
|
1060
|
+
});
|
|
1061
|
+
|
|
1062
|
+
test("a failing mail cannot break the write - the row is already committed", async () => {
|
|
1063
|
+
stubs.mail.mockRejectedValue(new Error("smtp is down") as never);
|
|
1064
|
+
|
|
1065
|
+
await expect(
|
|
1066
|
+
hooks().onCreateSuccess(
|
|
1067
|
+
{
|
|
1068
|
+
createBy: createBy({ userId: VICTIM_USER_ID }),
|
|
1069
|
+
carryForward: null,
|
|
1070
|
+
},
|
|
1071
|
+
persistedRule({ userId: VICTIM_USER_ID }),
|
|
1072
|
+
),
|
|
1073
|
+
).resolves.toBeDefined();
|
|
1074
|
+
|
|
1075
|
+
await flushAsync();
|
|
1076
|
+
});
|
|
1077
|
+
|
|
1078
|
+
test("a failing audit sink cannot break the write either", async () => {
|
|
1079
|
+
stubs.auditCreate.mockRejectedValue(
|
|
1080
|
+
new Error("clickhouse is down") as never,
|
|
1081
|
+
);
|
|
1082
|
+
|
|
1083
|
+
await expect(
|
|
1084
|
+
hooks().onCreateSuccess(
|
|
1085
|
+
{
|
|
1086
|
+
createBy: createBy({ userId: VICTIM_USER_ID }),
|
|
1087
|
+
carryForward: null,
|
|
1088
|
+
},
|
|
1089
|
+
persistedRule({ userId: VICTIM_USER_ID }),
|
|
1090
|
+
),
|
|
1091
|
+
).resolves.toBeDefined();
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1094
|
+
test("updating somebody else's rule audits it against the owner as the DATABASE had it", async () => {
|
|
1095
|
+
const before: UserNotificationRule = persistedRule({
|
|
1096
|
+
userId: VICTIM_USER_ID,
|
|
1097
|
+
});
|
|
1098
|
+
|
|
1099
|
+
await hooks().onUpdateSuccess(
|
|
1100
|
+
{
|
|
1101
|
+
updateBy: {
|
|
1102
|
+
query: { _id: RULE_ID.toString() },
|
|
1103
|
+
data: { userId: ADMIN_USER_ID, notifyAfterMinutes: 5 },
|
|
1104
|
+
limit: 1,
|
|
1105
|
+
skip: 0,
|
|
1106
|
+
props: memberProps(),
|
|
1107
|
+
},
|
|
1108
|
+
carryForward: { affectedRules: [before] },
|
|
1109
|
+
} as unknown as OnUpdate<UserNotificationRule>,
|
|
1110
|
+
[RULE_ID],
|
|
1111
|
+
);
|
|
1112
|
+
|
|
1113
|
+
expect(stubs.auditUpdate).toHaveBeenCalledTimes(1);
|
|
1114
|
+
|
|
1115
|
+
const auditArg: { before: UserNotificationRule; itemId: ObjectID } = stubs
|
|
1116
|
+
.auditUpdate.mock.calls[0]![0] as {
|
|
1117
|
+
before: UserNotificationRule;
|
|
1118
|
+
itemId: ObjectID;
|
|
1119
|
+
};
|
|
1120
|
+
|
|
1121
|
+
expect(auditArg.before.userId?.toString()).toBe(
|
|
1122
|
+
VICTIM_USER_ID.toString(),
|
|
1123
|
+
);
|
|
1124
|
+
expect(auditArg.itemId.toString()).toBe(RULE_ID.toString());
|
|
1125
|
+
});
|
|
1126
|
+
|
|
1127
|
+
test("one request touching many of one person's rules audits each but mails them once", async () => {
|
|
1128
|
+
/*
|
|
1129
|
+
* Twenty copies of "an admin changed your rules" is how a warning becomes
|
|
1130
|
+
* a filter rule. The audit stays per row because that is what an
|
|
1131
|
+
* investigator reconstructs from.
|
|
1132
|
+
*/
|
|
1133
|
+
await hooks().onUpdateSuccess(
|
|
1134
|
+
{
|
|
1135
|
+
updateBy: {
|
|
1136
|
+
query: { userId: VICTIM_USER_ID },
|
|
1137
|
+
data: { notifyAfterMinutes: 5 },
|
|
1138
|
+
limit: 10,
|
|
1139
|
+
skip: 0,
|
|
1140
|
+
props: memberProps(),
|
|
1141
|
+
},
|
|
1142
|
+
carryForward: {
|
|
1143
|
+
affectedRules: [
|
|
1144
|
+
persistedRule({ userId: VICTIM_USER_ID }),
|
|
1145
|
+
persistedRule({ userId: VICTIM_USER_ID }),
|
|
1146
|
+
persistedRule({ userId: VICTIM_USER_ID }),
|
|
1147
|
+
],
|
|
1148
|
+
},
|
|
1149
|
+
} as unknown as OnUpdate<UserNotificationRule>,
|
|
1150
|
+
[RULE_ID],
|
|
1151
|
+
);
|
|
1152
|
+
|
|
1153
|
+
await flushAsync();
|
|
1154
|
+
|
|
1155
|
+
expect(stubs.auditUpdate).toHaveBeenCalledTimes(3);
|
|
1156
|
+
expect(stubs.mail).toHaveBeenCalledTimes(1);
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1159
|
+
test("two owners in one update each get their own notification", async () => {
|
|
1160
|
+
await hooks().onUpdateSuccess(
|
|
1161
|
+
{
|
|
1162
|
+
updateBy: {
|
|
1163
|
+
query: {},
|
|
1164
|
+
data: { notifyAfterMinutes: 5 },
|
|
1165
|
+
limit: 10,
|
|
1166
|
+
skip: 0,
|
|
1167
|
+
props: memberProps(),
|
|
1168
|
+
},
|
|
1169
|
+
carryForward: {
|
|
1170
|
+
affectedRules: [
|
|
1171
|
+
persistedRule({ userId: VICTIM_USER_ID }),
|
|
1172
|
+
persistedRule({ userId: OUTSIDER_USER_ID }),
|
|
1173
|
+
],
|
|
1174
|
+
},
|
|
1175
|
+
} as unknown as OnUpdate<UserNotificationRule>,
|
|
1176
|
+
[RULE_ID],
|
|
1177
|
+
);
|
|
1178
|
+
|
|
1179
|
+
await flushAsync();
|
|
1180
|
+
|
|
1181
|
+
expect(stubs.mail).toHaveBeenCalledTimes(2);
|
|
1182
|
+
});
|
|
1183
|
+
|
|
1184
|
+
test("a rule the actor owns is skipped even when other rules in the same update are not", async () => {
|
|
1185
|
+
await hooks().onUpdateSuccess(
|
|
1186
|
+
{
|
|
1187
|
+
updateBy: {
|
|
1188
|
+
query: {},
|
|
1189
|
+
data: { notifyAfterMinutes: 5 },
|
|
1190
|
+
limit: 10,
|
|
1191
|
+
skip: 0,
|
|
1192
|
+
props: memberProps(),
|
|
1193
|
+
},
|
|
1194
|
+
carryForward: {
|
|
1195
|
+
affectedRules: [
|
|
1196
|
+
persistedRule({ userId: ADMIN_USER_ID }),
|
|
1197
|
+
persistedRule({ userId: VICTIM_USER_ID }),
|
|
1198
|
+
],
|
|
1199
|
+
},
|
|
1200
|
+
} as unknown as OnUpdate<UserNotificationRule>,
|
|
1201
|
+
[RULE_ID],
|
|
1202
|
+
);
|
|
1203
|
+
|
|
1204
|
+
await flushAsync();
|
|
1205
|
+
|
|
1206
|
+
expect(stubs.auditUpdate).toHaveBeenCalledTimes(1);
|
|
1207
|
+
expect(stubs.mail).toHaveBeenCalledTimes(1);
|
|
1208
|
+
});
|
|
1209
|
+
|
|
1210
|
+
test("an actor-less write (root, internal jobs) reports nothing", async () => {
|
|
1211
|
+
await hooks().onUpdateSuccess(
|
|
1212
|
+
{
|
|
1213
|
+
updateBy: {
|
|
1214
|
+
query: {},
|
|
1215
|
+
data: { notifyAfterMinutes: 5 },
|
|
1216
|
+
limit: 10,
|
|
1217
|
+
skip: 0,
|
|
1218
|
+
props: { isRoot: true },
|
|
1219
|
+
},
|
|
1220
|
+
carryForward: {
|
|
1221
|
+
affectedRules: [persistedRule({ userId: VICTIM_USER_ID })],
|
|
1222
|
+
},
|
|
1223
|
+
} as unknown as OnUpdate<UserNotificationRule>,
|
|
1224
|
+
[RULE_ID],
|
|
1225
|
+
);
|
|
1226
|
+
|
|
1227
|
+
await flushAsync();
|
|
1228
|
+
|
|
1229
|
+
expect(stubs.auditUpdate).not.toHaveBeenCalled();
|
|
1230
|
+
expect(stubs.mail).not.toHaveBeenCalled();
|
|
1231
|
+
});
|
|
1232
|
+
});
|
|
1233
|
+
|
|
1234
|
+
/*
|
|
1235
|
+
* ---------------------------------------------------------------------
|
|
1236
|
+
* Defence in depth - a hijacked row is inert at delivery time.
|
|
1237
|
+
* ---------------------------------------------------------------------
|
|
1238
|
+
*/
|
|
1239
|
+
describe("delivery refuses a rule whose method belongs to somebody else", () => {
|
|
1240
|
+
test("the rule load asks every method relation for its owner", async () => {
|
|
1241
|
+
stubs.ruleFindOneById.mockResolvedValue(
|
|
1242
|
+
persistedRule({ userId: VICTIM_USER_ID }) as never,
|
|
1243
|
+
);
|
|
1244
|
+
|
|
1245
|
+
await UserNotificationRuleService.executeNotificationRuleItem(
|
|
1246
|
+
RULE_ID,
|
|
1247
|
+
executeOptions(),
|
|
1248
|
+
);
|
|
1249
|
+
|
|
1250
|
+
const select: Record<string, { userId?: boolean }> = (
|
|
1251
|
+
stubs.ruleFindOneById.mock.calls[0]![0] as {
|
|
1252
|
+
select: Record<string, { userId?: boolean }>;
|
|
1253
|
+
}
|
|
1254
|
+
).select;
|
|
1255
|
+
|
|
1256
|
+
for (const channel of CHANNELS) {
|
|
1257
|
+
expect(select[channel.relationProperty]?.userId).toBe(true);
|
|
1258
|
+
}
|
|
1259
|
+
});
|
|
1260
|
+
|
|
1261
|
+
test.each(
|
|
1262
|
+
CHANNELS.map((channel: ChannelFixture): [string, ChannelFixture] => {
|
|
1263
|
+
return [channel.label, channel];
|
|
1264
|
+
}),
|
|
1265
|
+
)(
|
|
1266
|
+
"%s: a rule whose method belongs to another user sends nothing",
|
|
1267
|
+
async (_label: string, channel: ChannelFixture) => {
|
|
1268
|
+
stubs.ruleFindOneById.mockResolvedValue(
|
|
1269
|
+
persistedRule({
|
|
1270
|
+
userId: VICTIM_USER_ID,
|
|
1271
|
+
[channel.relationProperty]: {
|
|
1272
|
+
id: METHOD_ID,
|
|
1273
|
+
userId: ADMIN_USER_ID,
|
|
1274
|
+
},
|
|
1275
|
+
} as Partial<UserNotificationRule>) as never,
|
|
1276
|
+
);
|
|
1277
|
+
|
|
1278
|
+
await UserNotificationRuleService.executeNotificationRuleItem(
|
|
1279
|
+
RULE_ID,
|
|
1280
|
+
executeOptions(),
|
|
1281
|
+
);
|
|
1282
|
+
|
|
1283
|
+
expect(stubs.deliver).not.toHaveBeenCalled();
|
|
1284
|
+
|
|
1285
|
+
expect(timelineRows).toHaveLength(1);
|
|
1286
|
+
expect(timelineRows[0]!.status).toBe(UserNotificationStatus.Error);
|
|
1287
|
+
expect(timelineRows[0]!.statusMessage).toContain(channel.label);
|
|
1288
|
+
expect(timelineRows[0]!.statusMessage).toContain(
|
|
1289
|
+
"belongs to a different user",
|
|
1290
|
+
);
|
|
1291
|
+
/*
|
|
1292
|
+
* The timeline row is stamped with the RULE's owner - the person whose
|
|
1293
|
+
* page was refused - so it appears on the log they will be looking at.
|
|
1294
|
+
*/
|
|
1295
|
+
expect(timelineRows[0]!.userId?.toString()).toBe(
|
|
1296
|
+
VICTIM_USER_ID.toString(),
|
|
1297
|
+
);
|
|
1298
|
+
},
|
|
1299
|
+
);
|
|
1300
|
+
|
|
1301
|
+
test("one foreign method poisons the whole rule, including its honest channels", async () => {
|
|
1302
|
+
/*
|
|
1303
|
+
* A row with a foreign FK on it is not a row with one bad field; it is a
|
|
1304
|
+
* row somebody wrote to redirect a page. Delivering its other channels
|
|
1305
|
+
* would let it keep working well enough to escape notice.
|
|
1306
|
+
*/
|
|
1307
|
+
stubs.ruleFindOneById.mockResolvedValue(
|
|
1308
|
+
persistedRule({
|
|
1309
|
+
userId: VICTIM_USER_ID,
|
|
1310
|
+
userEmail: { id: METHOD_ID, userId: VICTIM_USER_ID },
|
|
1311
|
+
userWebhook: { id: METHOD_ID, userId: ADMIN_USER_ID },
|
|
1312
|
+
} as unknown as Partial<UserNotificationRule>) as never,
|
|
1313
|
+
);
|
|
1314
|
+
|
|
1315
|
+
await UserNotificationRuleService.executeNotificationRuleItem(
|
|
1316
|
+
RULE_ID,
|
|
1317
|
+
executeOptions(),
|
|
1318
|
+
);
|
|
1319
|
+
|
|
1320
|
+
expect(stubs.deliver).not.toHaveBeenCalled();
|
|
1321
|
+
expect(timelineRows[0]!.statusMessage).toContain("Webhook");
|
|
1322
|
+
});
|
|
1323
|
+
|
|
1324
|
+
test("a rule whose methods all belong to its owner is delivered normally", async () => {
|
|
1325
|
+
stubs.ruleFindOneById.mockResolvedValue(
|
|
1326
|
+
persistedRule({
|
|
1327
|
+
userId: VICTIM_USER_ID,
|
|
1328
|
+
userEmail: { id: METHOD_ID, userId: VICTIM_USER_ID },
|
|
1329
|
+
} as unknown as Partial<UserNotificationRule>) as never,
|
|
1330
|
+
);
|
|
1331
|
+
|
|
1332
|
+
await UserNotificationRuleService.executeNotificationRuleItem(
|
|
1333
|
+
RULE_ID,
|
|
1334
|
+
executeOptions(),
|
|
1335
|
+
);
|
|
1336
|
+
|
|
1337
|
+
expect(stubs.deliver).toHaveBeenCalledTimes(1);
|
|
1338
|
+
expect(timelineRows).toHaveLength(0);
|
|
1339
|
+
});
|
|
1340
|
+
|
|
1341
|
+
test("an unloaded owner column is NOT read as a mismatch - unknown must never drop a page", async () => {
|
|
1342
|
+
/*
|
|
1343
|
+
* The single most dangerous way to get this check wrong. `undefined`
|
|
1344
|
+
* means the column was not selected, not that it disagrees; treating the
|
|
1345
|
+
* two the same would turn a security guard into a page-dropping machine
|
|
1346
|
+
* on every caller that does not select userId - the exact failure this
|
|
1347
|
+
* whole epic exists to eliminate.
|
|
1348
|
+
*/
|
|
1349
|
+
stubs.ruleFindOneById.mockResolvedValue(
|
|
1350
|
+
persistedRule({
|
|
1351
|
+
userId: VICTIM_USER_ID,
|
|
1352
|
+
userEmail: { id: METHOD_ID },
|
|
1353
|
+
} as unknown as Partial<UserNotificationRule>) as never,
|
|
1354
|
+
);
|
|
1355
|
+
|
|
1356
|
+
await UserNotificationRuleService.executeNotificationRuleItem(
|
|
1357
|
+
RULE_ID,
|
|
1358
|
+
executeOptions(),
|
|
1359
|
+
);
|
|
1360
|
+
|
|
1361
|
+
expect(stubs.deliver).toHaveBeenCalledTimes(1);
|
|
1362
|
+
expect(timelineRows).toHaveLength(0);
|
|
1363
|
+
});
|
|
1364
|
+
|
|
1365
|
+
test("a rule with no owner at all is still delivered - there is nothing to compare it against", async () => {
|
|
1366
|
+
stubs.ruleFindOneById.mockResolvedValue(
|
|
1367
|
+
persistedRule({
|
|
1368
|
+
userId: undefined,
|
|
1369
|
+
userEmail: { id: METHOD_ID, userId: ADMIN_USER_ID },
|
|
1370
|
+
} as unknown as Partial<UserNotificationRule>) as never,
|
|
1371
|
+
);
|
|
1372
|
+
|
|
1373
|
+
await UserNotificationRuleService.executeNotificationRuleItem(
|
|
1374
|
+
RULE_ID,
|
|
1375
|
+
executeOptions(),
|
|
1376
|
+
);
|
|
1377
|
+
|
|
1378
|
+
expect(stubs.deliver).toHaveBeenCalledTimes(1);
|
|
1379
|
+
});
|
|
1380
|
+
|
|
1381
|
+
test("the claim is still taken first, and a missing rule still throws", async () => {
|
|
1382
|
+
stubs.ruleFindOneById.mockResolvedValue(null as never);
|
|
1383
|
+
|
|
1384
|
+
await expect(
|
|
1385
|
+
UserNotificationRuleService.executeNotificationRuleItem(
|
|
1386
|
+
RULE_ID,
|
|
1387
|
+
executeOptions(),
|
|
1388
|
+
),
|
|
1389
|
+
).rejects.toThrow(Exception);
|
|
1390
|
+
|
|
1391
|
+
expect(stubs.claim).toHaveBeenCalledTimes(1);
|
|
1392
|
+
});
|
|
1393
|
+
});
|
|
1394
|
+
});
|