@oneuptime/common 14.0.6 → 14.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/Incident.ts +267 -1
- package/Models/DatabaseModels/IncidentCustomField.ts +202 -0
- package/Models/DatabaseModels/IncidentEpisode.ts +30 -0
- package/Models/DatabaseModels/IncidentTemplate.ts +126 -1
- package/Models/DatabaseModels/InventoryItem.ts +23 -12
- package/Models/DatabaseModels/StatusPage.ts +55 -0
- package/Models/DatabaseModels/StatusPageSubscriber.ts +128 -0
- package/Server/API/IncidentAPI.ts +162 -0
- package/Server/API/StatusPageAPI.ts +487 -171
- package/Server/API/StatusPageSubscriberAPI.ts +63 -15
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.ts +217 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.ts +49 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.ts +108 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.ts +33 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.ts +51 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.ts +33 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.ts +168 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.ts +44 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +16 -0
- package/Server/Infrastructure/Semaphore.ts +10 -0
- package/Server/Middleware/IdentityRateLimit.ts +108 -3
- package/Server/Middleware/SubscriberNotificationPreviewRateLimit.ts +237 -0
- package/Server/Middleware/SubscriberNotificationTestSendRateLimit.ts +304 -0
- package/Server/Services/AlertService.ts +32 -36
- package/Server/Services/DatabaseService.ts +93 -10
- package/Server/Services/IncidentAlertService.ts +609 -2
- package/Server/Services/IncidentCustomFieldService.ts +276 -1
- package/Server/Services/IncidentEpisodePublicNoteService.ts +28 -0
- package/Server/Services/IncidentEpisodeService.ts +15 -0
- package/Server/Services/IncidentEpisodeStateTimelineService.ts +27 -1
- package/Server/Services/IncidentPublicNoteService.ts +30 -1
- package/Server/Services/IncidentService.ts +1663 -103
- package/Server/Services/IncidentStateTimelineService.ts +13 -0
- package/Server/Services/IncidentTemplateService.ts +109 -0
- package/Server/Services/InventoryItemService.ts +38 -0
- package/Server/Services/IoTDeviceCredentialService.ts +13 -4
- package/Server/Services/ScheduledMaintenancePublicNoteService.ts +30 -1
- package/Server/Services/ScheduledMaintenanceService.ts +118 -64
- package/Server/Services/SmsService.ts +10 -0
- package/Server/Services/StatusPageService.ts +65 -8
- package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +299 -37
- package/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.ts +129 -0
- package/Server/Services/StatusPageSubscriberService.ts +1161 -64
- package/Server/Services/UserNotificationRuleService.ts +55 -53
- package/Server/Services/WorkspaceNotificationSummaryService.ts +38 -20
- package/Server/Types/Markdown.ts +153 -6
- package/Server/Types/Workflow/Components/Email.ts +61 -2
- package/Server/Utils/AffectedResources/LinkedAffectedResources.ts +576 -0
- package/Server/Utils/Alert/AlertStateChangeAuthorization.ts +129 -0
- package/Server/Utils/CustomField/CustomFieldRename.ts +378 -0
- package/Server/Utils/DataSource/EgressGuard.ts +66 -21
- package/Server/Utils/Inventory/InventoryOverviewAggregation.ts +111 -0
- package/Server/Utils/Mail/PinnedSmtpSocket.ts +130 -0
- package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +10 -0
- package/Server/Utils/Response.ts +31 -0
- package/Server/Utils/SSRFProtection.ts +76 -16
- package/Server/Utils/StatusPage/IncidentStatusPageScope.ts +948 -0
- package/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.ts +656 -0
- package/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.ts +805 -0
- package/Server/Utils/StatusPage/StatusPageExclusion.ts +18 -0
- package/Server/Utils/StatusPage/StatusPageReadAccess.ts +125 -0
- package/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.ts +151 -0
- package/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.ts +70 -0
- package/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.ts +421 -0
- package/Server/Utils/StatusPage/SubscriberNotificationClaim.ts +107 -0
- package/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.ts +1049 -0
- package/Server/Utils/StatusPage/SubscriberNotificationFanOut.ts +180 -0
- package/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.ts +839 -0
- package/Server/Utils/StatusPage/SubscriberNotificationResendAccess.ts +393 -0
- package/Server/Utils/StatusPage/SubscriberNotificationRunLimit.ts +246 -0
- package/Server/Utils/StatusPage/SubscriberNotificationTiming.ts +133 -0
- package/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.ts +215 -0
- package/Server/Utils/StatusPageResource.ts +51 -20
- package/Server/Utils/Topology/TopologyConcurrencyLimiter.ts +229 -0
- package/Server/Utils/Topology/TopologyQueries.ts +1350 -0
- package/Server/Utils/Topology/TopologyRequest.ts +463 -0
- package/Server/Utils/Topology/TopologyResponseCache.ts +183 -0
- package/Server/Utils/Topology/TopologySql.ts +1024 -0
- package/Server/Utils/VM/VMAPI.ts +1 -0
- package/Server/Utils/VM/VMRunner.ts +10 -0
- package/Server/Utils/Workflow/OAuth2TokenClient.ts +10 -0
- package/Server/Views/Partials/SensitiveUrlToken.ejs +8 -5
- package/Tests/App/Accounts/RegisterVerifyEmail.test.tsx +748 -0
- package/Tests/App/Accounts/ResendVerificationEmail.test.tsx +1222 -0
- package/Tests/App/Accounts/VerificationEmailResendUtils.test.ts +797 -0
- package/Tests/App/Accounts/VerifyEmailPending.test.tsx +538 -0
- package/Tests/App/Accounts/VerifyEmailResend.test.tsx +779 -0
- package/Tests/App/Dashboard/AcknowledgeAlertsOnDeclare.test.ts +369 -0
- package/Tests/App/Dashboard/AffectedResourcesSlo.test.tsx +1 -1
- package/Tests/App/Dashboard/AffectedResourcesSummaryStep.test.tsx +510 -123
- package/Tests/App/Dashboard/AlertsTableAffectedResources.test.tsx +490 -0
- package/Tests/App/Dashboard/ChangeAlertStateDeclareIncident.test.tsx +886 -0
- package/Tests/App/Dashboard/CreateReviewStepRoleSummary.test.tsx +1250 -0
- package/Tests/App/Dashboard/CreateReviewStepSelectionSummary.test.tsx +738 -0
- package/Tests/App/Dashboard/CustomFieldsSettingsIncidentOptions.test.tsx +357 -0
- package/Tests/App/Dashboard/DeclareIncidentFromAlert.test.ts +417 -0
- package/Tests/App/Dashboard/EntityDetailPanel.test.tsx +1948 -229
- package/Tests/App/Dashboard/EntityDetailPanelLifecycle.test.tsx +493 -0
- package/Tests/App/Dashboard/EntityDetailPanelNavigation.test.tsx +439 -0
- package/Tests/App/Dashboard/EventNotes.test.tsx +776 -0
- package/Tests/App/Dashboard/EventNotesPages.test.tsx +119 -0
- package/Tests/App/Dashboard/EventNotesUtil.test.ts +116 -17
- package/Tests/App/Dashboard/EventOverviewPages.test.tsx +534 -6
- package/Tests/App/Dashboard/EventStatusPanel.test.tsx +798 -2
- package/Tests/App/Dashboard/FetchStateAndSeverityElements.test.tsx +669 -0
- package/Tests/App/Dashboard/FetchUsers.test.tsx +230 -0
- package/Tests/App/Dashboard/IncidentChangeStateNotifyDefault.test.tsx +91 -1
- package/Tests/App/Dashboard/IncidentCreateAcknowledgeAlerts.test.tsx +1611 -0
- package/Tests/App/Dashboard/IncidentCreateDetailsStep.test.tsx +643 -0
- package/Tests/App/Dashboard/IncidentCreateFromAlerts.test.tsx +8 -1
- package/Tests/App/Dashboard/IncidentCreateStatusPageScope.test.tsx +601 -0
- package/Tests/App/Dashboard/IncidentCustomFieldTemplateVariables.test.tsx +386 -0
- package/Tests/App/Dashboard/IncidentNoteTemplatePlaceholders.test.tsx +124 -0
- package/Tests/App/Dashboard/IncidentNoteTemplateVariablesLoader.test.ts +305 -0
- package/Tests/App/Dashboard/IncidentOverviewNotifyDefault.test.tsx +112 -0
- package/Tests/App/Dashboard/IncidentSettingsRenotifyOnPublish.test.tsx +674 -0
- package/Tests/App/Dashboard/IncidentSettingsStatusPageScope.test.tsx +1051 -0
- package/Tests/App/Dashboard/IncidentTemplateCustomFields.test.tsx +384 -0
- package/Tests/App/Dashboard/IncidentTemplateStatusPageScopeCard.test.tsx +215 -0
- package/Tests/App/Dashboard/InfrastructureExplorer.test.tsx +1365 -57
- package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +964 -23
- package/Tests/App/Dashboard/InventoryRelationships.test.tsx +885 -0
- package/Tests/App/Dashboard/PublicNotePagesNotifyDefault.test.tsx +113 -1
- package/Tests/App/Dashboard/ServiceMapDatabaseLink.test.tsx +201 -15
- package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +508 -36
- package/Tests/App/Dashboard/SubscriberAudienceSummary.test.tsx +512 -0
- package/Tests/App/Dashboard/SubscriberNotificationPreviewModal.test.tsx +837 -0
- package/Tests/App/Dashboard/SubscriberNotificationStatusLabel.test.tsx +88 -0
- package/Tests/App/Dashboard/SubscriberNotificationStatusResend.test.tsx +473 -0
- package/Tests/App/Dashboard/SubscriberTemplateLivePreview.test.tsx +196 -0
- package/Tests/App/Dashboard/TeamAddedSubscribersUnsubscribedNotice.test.tsx +248 -0
- package/Tests/App/Dashboard/TopologyDataLifecycle.test.tsx +583 -0
- package/Tests/App/Dashboard/TopologyDataLoading.test.tsx +982 -218
- package/Tests/App/Dashboard/TopologyPageNavigation.test.tsx +818 -114
- package/Tests/App/SensitiveUrlTokenBootstrap.test.ts +64 -3
- package/Tests/App/StatusPage/StatusPageUnsubscribePage.test.tsx +412 -0
- package/Tests/Models/IncidentCustomFieldApiSchemaContract.test.ts +217 -0
- package/Tests/Models/IncidentCustomFieldCreateSettingsColumns.test.ts +252 -0
- package/Tests/Models/StatusPageSubscriberUnsubscribeColumns.test.ts +218 -0
- package/Tests/Server/API/ExpiredSessionRouteGuards.test.ts +7 -0
- package/Tests/Server/API/IncidentSubscriberAudienceAPI.test.ts +1408 -0
- package/Tests/Server/API/StatusPageIncidentScope.test.ts +1868 -0
- package/Tests/Server/API/StatusPageIncidentScopePostgres.test.ts +790 -0
- package/Tests/Server/API/StatusPageManageSubscriptionRecipient.test.ts +10 -1
- package/Tests/Server/API/StatusPageManageSubscriptionTemplateVariables.test.ts +206 -18
- package/Tests/Server/API/StatusPagePostmortemVisibility.test.ts +14 -2
- package/Tests/Server/API/StatusPageSubscriberCreateResponse.test.ts +217 -0
- package/Tests/Server/API/StatusPageSubscriberOwnership.test.ts +103 -2
- package/Tests/Server/API/StatusPageUnsubscribeAPI.test.ts +396 -0
- package/Tests/Server/Infrastructure/Postgres/AddIncidentStatusPageScopeMigration.test.ts +395 -0
- package/Tests/Server/Infrastructure/Postgres/AddIncidentTemplateStatusPageScopeFlagMigration.test.ts +150 -0
- package/Tests/Server/Infrastructure/Postgres/AddSubscriberNotificationClaimedAtMigration.test.ts +156 -0
- package/Tests/Server/Infrastructure/Postgres/RenameInventoryItemMigration.test.ts +90 -21
- package/Tests/Server/Infrastructure/Postgres/SkipHiddenIncidentCreatedNotificationsMigration.test.ts +410 -0
- package/Tests/Server/Middleware/IdentityRateLimit.test.ts +551 -1
- package/Tests/Server/Middleware/SubscriberNotificationPreviewRateLimit.test.ts +238 -0
- package/Tests/Server/Middleware/SubscriberNotificationTestSendRateLimit.test.ts +245 -0
- package/Tests/Server/Services/AddStatusPageSubscriberUnsubscribeTokenMigration.test.ts +152 -0
- package/Tests/Server/Services/CrossProjectRelationWriteGuard.test.ts +221 -36
- package/Tests/Server/Services/DatabaseServiceCompareAndSetColumns.test.ts +191 -0
- package/Tests/Server/Services/DomainService.test.ts +525 -0
- package/Tests/Server/Services/IncidentAlertAcknowledgeOnDeclare.test.ts +2630 -0
- package/Tests/Server/Services/IncidentAlertPostgres.test.ts +1625 -1
- package/Tests/Server/Services/IncidentCreatedRenotifyOnPublish.test.ts +681 -0
- package/Tests/Server/Services/IncidentCreatedResend.test.ts +775 -0
- package/Tests/Server/Services/IncidentCustomFieldRenamePostgres.test.ts +713 -0
- package/Tests/Server/Services/IncidentCustomFieldService.test.ts +751 -0
- package/Tests/Server/Services/IncidentCustomFieldValueValidation.test.ts +556 -0
- package/Tests/Server/Services/IncidentDeclareAcknowledgeAlerts.test.ts +2189 -0
- package/Tests/Server/Services/IncidentStatusPageScope.test.ts +2971 -0
- package/Tests/Server/Services/IncidentStatusPageScopeFeed.test.ts +131 -0
- package/Tests/Server/Services/IncidentStatusPageScopePostgres.test.ts +625 -0
- package/Tests/Server/Services/IncidentTemplateCustomFieldsCopy.test.ts +311 -0
- package/Tests/Server/Services/IncidentTemplateStatusPageScope.test.ts +363 -0
- package/Tests/Server/Services/InventoryItemServiceOverviewCounts.test.ts +345 -0
- package/Tests/Server/Services/InventoryOverviewPostgres.test.ts +254 -0
- package/Tests/Server/Services/IoTDeviceCredentialService.test.ts +819 -0
- package/Tests/Server/Services/LinkedAffectedResourcesCreatedFeed.test.ts +420 -0
- package/Tests/Server/Services/PublicNoteEditFeedEventType.test.ts +213 -0
- package/Tests/Server/Services/PublicNoteResendAccess.test.ts +994 -0
- package/Tests/Server/Services/ScheduledMaintenanceSubscriberTemplateFormats.test.ts +289 -9
- package/Tests/Server/Services/SloAffectedResourceCreatedFeed.test.ts +92 -36
- package/Tests/Server/Services/SmsServiceFailIfNotSent.test.ts +67 -0
- package/Tests/Server/Services/StatusPageReportIncidentCounts.test.ts +610 -0
- package/Tests/Server/Services/StatusPageSubscriberAudienceCounts.test.ts +201 -0
- package/Tests/Server/Services/StatusPageSubscriberAudienceCountsPostgres.test.ts +271 -0
- package/Tests/Server/Services/StatusPageSubscriberConfirmationTemplateEscaping.test.ts +200 -0
- package/Tests/Server/Services/StatusPageSubscriberNotificationLogRedaction.test.ts +54 -0
- package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateCompile.test.ts +37 -0
- package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateIncidentRecordAccess.test.ts +772 -0
- package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateLookup.test.ts +506 -0
- package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateLookupPostgres.test.ts +261 -0
- package/Tests/Server/Services/StatusPageSubscriberReport.test.ts +21 -5
- package/Tests/Server/Services/StatusPageSubscriberUnsubscribe.test.ts +1618 -0
- package/Tests/Server/Services/StatusPageSubscriberUnsubscribeLinkSenders.test.ts +373 -0
- package/Tests/Server/Services/StatusPageSubscriberUnsubscribePostgres.test.ts +388 -0
- package/Tests/Server/Services/SubscriberNotificationInFlightRequeue.test.ts +500 -0
- package/Tests/Server/Types/MarkdownEmailLinks.test.ts +320 -0
- package/Tests/Server/Types/Workflow/Components/Email.test.ts +491 -1
- package/Tests/Server/Types/Workflow/Components/EmailSmtpPinning.test.ts +398 -0
- package/Tests/Server/Utils/AffectedResources/LinkedAffectedResources.test.ts +858 -0
- package/Tests/Server/Utils/Alert/AlertStateChangeAuthorization.test.ts +1172 -0
- package/Tests/Server/Utils/CustomField/CustomFieldRename.test.ts +586 -0
- package/Tests/Server/Utils/DataSource/EgressGuard.test.ts +246 -10
- package/Tests/Server/Utils/DataSource/EgressGuardDeploymentConfig.test.ts +226 -0
- package/Tests/Server/Utils/DataSource/EgressGuardPinning.test.ts +131 -0
- package/Tests/Server/Utils/DataSource/HttpFetch.test.ts +4 -0
- package/Tests/Server/Utils/EgressPolicyEnvironment.ts +45 -0
- package/Tests/Server/Utils/Mail/PinnedSmtpSocket.test.ts +409 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaObservationBuilderUnits.test.ts +23 -0
- package/Tests/Server/Utils/SSRFProtection.test.ts +4 -0
- package/Tests/Server/Utils/SSRFProtectionCloudServiceAddresses.test.ts +4 -0
- package/Tests/Server/Utils/SSRFProtectionPrivateNetwork.test.ts +4 -0
- package/Tests/Server/Utils/SSRFProtectionResolutionDetail.test.ts +314 -0
- package/Tests/Server/Utils/StatusPage/IncidentStatusPageScope.test.ts +1699 -0
- package/Tests/Server/Utils/StatusPage/IncidentStatusPageScopeQueriesPostgres.test.ts +508 -0
- package/Tests/Server/Utils/StatusPage/IncidentSubscriberAudienceCallerPermission.test.ts +246 -0
- package/Tests/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.test.ts +1348 -0
- package/Tests/Server/Utils/StatusPage/StatusPageReadAccess.test.ts +221 -0
- package/Tests/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.test.ts +132 -0
- package/Tests/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.test.ts +155 -0
- package/Tests/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.test.ts +708 -0
- package/Tests/Server/Utils/StatusPage/SubscriberNotificationClaim.test.ts +255 -0
- package/Tests/Server/Utils/StatusPage/SubscriberNotificationClaimPostgres.test.ts +528 -0
- package/Tests/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.test.ts +1238 -0
- package/Tests/Server/Utils/StatusPage/SubscriberNotificationFanOut.test.ts +472 -0
- package/Tests/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.test.ts +1168 -0
- package/Tests/Server/Utils/StatusPage/SubscriberNotificationRunLimit.test.ts +275 -0
- package/Tests/Server/Utils/StatusPage/SubscriberNotificationTiming.test.ts +131 -0
- package/Tests/Server/Utils/StatusPageResource.test.ts +98 -0
- package/Tests/Server/Utils/Topology/InventoryItemEntityKeyIndexMigration.test.ts +386 -0
- package/Tests/Server/Utils/Topology/InventoryItemEntityKeyIndexMigrationPostgres.test.ts +430 -0
- package/Tests/Server/Utils/Topology/TopologyActivityParityPostgres.test.ts +279 -0
- package/Tests/Server/Utils/Topology/TopologyConcurrencyLimiter.test.ts +534 -0
- package/Tests/Server/Utils/Topology/TopologyEstateGenerator.ts +624 -0
- package/Tests/Server/Utils/Topology/TopologyJsonResponse.test.ts +90 -0
- package/Tests/Server/Utils/Topology/TopologyQueries.test.ts +1634 -0
- package/Tests/Server/Utils/Topology/TopologyQueriesDifferentialPostgres.test.ts +683 -0
- package/Tests/Server/Utils/Topology/TopologyQueriesPostgres.test.ts +2624 -0
- package/Tests/Server/Utils/Topology/TopologyReference.ts +1018 -0
- package/Tests/Server/Utils/Topology/TopologyRequest.test.ts +643 -0
- package/Tests/Server/Utils/Topology/TopologyResponseCache.test.ts +305 -0
- package/Tests/Server/Utils/Topology/TopologySql.test.ts +973 -0
- package/Tests/Server/Utils/VM/VMRunnerPrivateNetworkWiring.test.ts +17 -0
- package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +75 -0
- package/Tests/Types/CustomField/CustomFieldSavedViews.test.ts +247 -0
- package/Tests/Types/CustomField/CustomFieldTemplateValues.test.ts +186 -0
- package/Tests/Types/CustomField/CustomFieldTypeCorrespondence.test.ts +48 -0
- package/Tests/Types/CustomField/CustomFieldValueFormat.test.ts +215 -0
- package/Tests/Types/CustomField/CustomFieldValueValidator.test.ts +343 -0
- package/Tests/Types/CustomField/CustomFieldVariableKey.test.ts +364 -0
- package/Tests/Types/SafeHtml.test.ts +102 -0
- package/Tests/Types/StatusPage/IncidentCreatedRenotify.test.ts +419 -0
- package/Tests/Types/StatusPage/IncidentCreatedResend.test.ts +118 -0
- package/Tests/Types/StatusPage/IncidentScopeAddedPagesNotification.test.ts +558 -0
- package/Tests/Types/StatusPage/IncidentSubscriberAudience.test.ts +284 -0
- package/Tests/Types/StatusPage/StatusPageSubscriberUnsubscribe.test.ts +341 -0
- package/Tests/Types/StatusPage/SubscriberNotificationPreview.test.ts +196 -0
- package/Tests/Types/StatusPage/SubscriberNotificationResend.test.ts +201 -0
- package/Tests/Types/StatusPage/SubscriberNotificationTemplateCompiler.test.ts +277 -0
- package/Tests/Types/StatusPage/SubscriberNotificationTemplatePreview.test.ts +179 -0
- package/Tests/Types/StatusPage/SubscriberNotificationTemplateVariables.test.ts +324 -0
- package/Tests/Types/StatusPage/SubscriberUpdateNotification.test.ts +60 -13
- package/Tests/Types/Topology/TopologyTypeRules.test.ts +176 -0
- package/Tests/UI/Components/CustomFields/CustomFieldFormFields.test.ts +432 -0
- package/Tests/UI/Components/CustomFields/CustomFieldModelFormFields.test.tsx +685 -0
- package/Tests/UI/Components/CustomFields/CustomFieldsDetailOrder.test.tsx +469 -0
- package/Tests/UI/Components/Forms/ModelFormCustomFieldInputs.test.tsx +325 -0
- package/Tests/UI/Components/Forms/ModelFormDropdownOptionsRefused.test.tsx +358 -0
- package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +22 -0
- package/Tests/UI/Monitor/NetworkPathView.test.tsx +289 -0
- package/Tests/Utils/Incident/IncidentFromAlerts.test.ts +302 -0
- package/Tests/Utils/Incident/IncidentNoteTemplateVariables.test.ts +623 -0
- package/Tests/Utils/Markdown/MarkdownEscape.test.ts +89 -0
- package/Tests/Utils/ProbeNetworkFailureUtil.test.ts +992 -0
- package/Tests/Utils/RequestFailedDetails.test.ts +575 -0
- package/Types/CustomField/CustomFieldOrder.ts +55 -0
- package/Types/CustomField/CustomFieldSavedViews.ts +225 -0
- package/Types/CustomField/CustomFieldTemplateMerge.ts +71 -0
- package/Types/CustomField/CustomFieldType.ts +14 -0
- package/Types/CustomField/CustomFieldValueFormat.ts +161 -0
- package/Types/CustomField/CustomFieldValueValidator.ts +488 -0
- package/Types/CustomField/CustomFieldVariableKey.ts +211 -0
- package/Types/Incident/IncidentAlertLink.ts +19 -0
- package/Types/SafeHtml.ts +93 -0
- package/Types/StatusPage/IncidentCreatedRenotify.ts +224 -0
- package/Types/StatusPage/IncidentCreatedResend.ts +108 -0
- package/Types/StatusPage/IncidentScopeAddedPagesNotification.ts +343 -0
- package/Types/StatusPage/IncidentSubscriberAudience.ts +332 -0
- package/Types/StatusPage/StatusPageSubscriberUnsubscribe.ts +405 -0
- package/Types/StatusPage/SubscriberNotificationInterruption.ts +32 -0
- package/Types/StatusPage/SubscriberNotificationPreview.ts +322 -0
- package/Types/StatusPage/SubscriberNotificationResend.ts +132 -0
- package/Types/StatusPage/SubscriberNotificationTemplateCompiler.ts +180 -0
- package/Types/StatusPage/SubscriberNotificationTemplatePreview.ts +301 -0
- package/Types/StatusPage/SubscriberNotificationTemplateVariables.ts +217 -4
- package/Types/StatusPage/SubscriberUpdateNotification.ts +32 -11
- package/Types/Telemetry/InventoryLiveness.ts +26 -0
- package/Types/Topology/TopologyApi.ts +428 -0
- package/Types/Topology/TopologyTypeRules.ts +191 -0
- package/UI/Components/CustomFields/CustomFieldFormFields.ts +317 -0
- package/UI/Components/CustomFields/CustomFieldModelFormFields.tsx +395 -0
- package/UI/Components/CustomFields/CustomFieldsDetail.tsx +95 -55
- package/UI/Components/Forms/ModelForm.tsx +70 -8
- package/UI/Components/Forms/Validation.ts +1 -1
- package/UI/Components/ModelFormModal/ModelFormModal.tsx +2 -4
- package/UI/Components/ModelTable/AnalyticsModelTable.tsx +2 -6
- package/UI/Components/ModelTable/BaseModelTable.tsx +9 -8
- package/UI/Components/ModelTable/CustomFieldColumns.tsx +14 -1
- package/UI/Components/ModelTable/ModelTable.tsx +6 -7
- package/UI/Utils/SensitiveUrlToken.ts +10 -5
- package/Utils/API.ts +247 -2
- package/Utils/Incident/IncidentFromAlerts.ts +78 -0
- package/Utils/Incident/IncidentNoteTemplateVariables.ts +304 -0
- package/Utils/Markdown/MarkdownEscape.ts +65 -0
- package/Utils/ProbeNetworkFailureUtil.ts +673 -0
- package/build/dist/Models/DatabaseModels/Incident.js +267 -1
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentCustomField.js +207 -1
- package/build/dist/Models/DatabaseModels/IncidentCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisode.js +31 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentTemplate.js +124 -1
- package/build/dist/Models/DatabaseModels/IncidentTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/InventoryItem.js +20 -11
- package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +56 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js +131 -0
- package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js.map +1 -1
- package/build/dist/Server/API/IncidentAPI.js +100 -1
- package/build/dist/Server/API/IncidentAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +417 -176
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageSubscriberAPI.js +52 -13
- package/build/dist/Server/API/StatusPageSubscriberAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.js +165 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.js +41 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.js +57 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.js +23 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.js +40 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.js +26 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.js +89 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.js +29 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/Semaphore.js +3 -0
- package/build/dist/Server/Infrastructure/Semaphore.js.map +1 -1
- package/build/dist/Server/Middleware/IdentityRateLimit.js +89 -3
- package/build/dist/Server/Middleware/IdentityRateLimit.js.map +1 -1
- package/build/dist/Server/Middleware/SubscriberNotificationPreviewRateLimit.js +149 -0
- package/build/dist/Server/Middleware/SubscriberNotificationPreviewRateLimit.js.map +1 -0
- package/build/dist/Server/Middleware/SubscriberNotificationTestSendRateLimit.js +190 -0
- package/build/dist/Server/Middleware/SubscriberNotificationTestSendRateLimit.js.map +1 -0
- package/build/dist/Server/Services/AlertService.js +21 -33
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +56 -7
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/IncidentAlertService.js +457 -3
- package/build/dist/Server/Services/IncidentAlertService.js.map +1 -1
- package/build/dist/Server/Services/IncidentCustomFieldService.js +204 -8
- package/build/dist/Server/Services/IncidentCustomFieldService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js +24 -0
- package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodeService.js +14 -0
- package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js +26 -0
- package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentPublicNoteService.js +26 -1
- package/build/dist/Server/Services/IncidentPublicNoteService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +1148 -42
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/IncidentStateTimelineService.js +12 -0
- package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentTemplateService.js +83 -0
- package/build/dist/Server/Services/IncidentTemplateService.js.map +1 -1
- package/build/dist/Server/Services/InventoryItemService.js +31 -0
- package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
- package/build/dist/Server/Services/IoTDeviceCredentialService.js +12 -5
- package/build/dist/Server/Services/IoTDeviceCredentialService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js +26 -1
- package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceService.js +84 -57
- package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
- package/build/dist/Server/Services/SmsService.js +2 -22
- package/build/dist/Server/Services/SmsService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +47 -9
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +237 -32
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.js +109 -0
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberService.js +925 -99
- package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +55 -51
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js +42 -25
- package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js.map +1 -1
- package/build/dist/Server/Types/Markdown.js +110 -6
- package/build/dist/Server/Types/Markdown.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/Email.js +55 -6
- package/build/dist/Server/Types/Workflow/Components/Email.js.map +1 -1
- package/build/dist/Server/Utils/AffectedResources/LinkedAffectedResources.js +405 -0
- package/build/dist/Server/Utils/AffectedResources/LinkedAffectedResources.js.map +1 -0
- package/build/dist/Server/Utils/Alert/AlertStateChangeAuthorization.js +112 -0
- package/build/dist/Server/Utils/Alert/AlertStateChangeAuthorization.js.map +1 -0
- package/build/dist/Server/Utils/CustomField/CustomFieldRename.js +211 -0
- package/build/dist/Server/Utils/CustomField/CustomFieldRename.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/EgressGuard.js +41 -12
- package/build/dist/Server/Utils/DataSource/EgressGuard.js.map +1 -1
- package/build/dist/Server/Utils/Inventory/InventoryOverviewAggregation.js +81 -0
- package/build/dist/Server/Utils/Inventory/InventoryOverviewAggregation.js.map +1 -0
- package/build/dist/Server/Utils/Mail/PinnedSmtpSocket.js +101 -0
- package/build/dist/Server/Utils/Mail/PinnedSmtpSocket.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +9 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Response.js +30 -0
- package/build/dist/Server/Utils/Response.js.map +1 -1
- package/build/dist/Server/Utils/SSRFProtection.js +48 -6
- package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
- package/build/dist/Server/Utils/StatusPage/IncidentStatusPageScope.js +631 -0
- package/build/dist/Server/Utils/StatusPage/IncidentStatusPageScope.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.js +403 -0
- package/build/dist/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.js +451 -0
- package/build/dist/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageExclusion.js +13 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageExclusion.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageReadAccess.js +91 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageReadAccess.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.js +101 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.js +47 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.js +273 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationClaim.js +73 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationClaim.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.js +722 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationFanOut.js +102 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationFanOut.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.js +527 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationResendAccess.js +278 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationResendAccess.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationRunLimit.js +137 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationRunLimit.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationTiming.js +103 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationTiming.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.js +114 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.js.map +1 -0
- package/build/dist/Server/Utils/StatusPageResource.js +39 -7
- package/build/dist/Server/Utils/StatusPageResource.js.map +1 -1
- package/build/dist/Server/Utils/Topology/TopologyConcurrencyLimiter.js +178 -0
- package/build/dist/Server/Utils/Topology/TopologyConcurrencyLimiter.js.map +1 -0
- package/build/dist/Server/Utils/Topology/TopologyQueries.js +884 -0
- package/build/dist/Server/Utils/Topology/TopologyQueries.js.map +1 -0
- package/build/dist/Server/Utils/Topology/TopologyRequest.js +275 -0
- package/build/dist/Server/Utils/Topology/TopologyRequest.js.map +1 -0
- package/build/dist/Server/Utils/Topology/TopologyResponseCache.js +146 -0
- package/build/dist/Server/Utils/Topology/TopologyResponseCache.js.map +1 -0
- package/build/dist/Server/Utils/Topology/TopologySql.js +688 -0
- package/build/dist/Server/Utils/Topology/TopologySql.js.map +1 -0
- package/build/dist/Server/Utils/VM/VMAPI.js.map +1 -1
- package/build/dist/Server/Utils/VM/VMRunner.js +1 -0
- package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
- package/build/dist/Server/Utils/Workflow/OAuth2TokenClient.js +3 -0
- package/build/dist/Server/Utils/Workflow/OAuth2TokenClient.js.map +1 -1
- package/build/dist/Types/CustomField/CustomFieldOrder.js +36 -0
- package/build/dist/Types/CustomField/CustomFieldOrder.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldSavedViews.js +135 -0
- package/build/dist/Types/CustomField/CustomFieldSavedViews.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldTemplateMerge.js +27 -0
- package/build/dist/Types/CustomField/CustomFieldTemplateMerge.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldType.js +12 -0
- package/build/dist/Types/CustomField/CustomFieldType.js.map +1 -1
- package/build/dist/Types/CustomField/CustomFieldValueFormat.js +126 -0
- package/build/dist/Types/CustomField/CustomFieldValueFormat.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldValueValidator.js +280 -0
- package/build/dist/Types/CustomField/CustomFieldValueValidator.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldVariableKey.js +150 -0
- package/build/dist/Types/CustomField/CustomFieldVariableKey.js.map +1 -0
- package/build/dist/Types/Incident/IncidentAlertLink.js +17 -0
- package/build/dist/Types/Incident/IncidentAlertLink.js.map +1 -1
- package/build/dist/Types/SafeHtml.js +84 -0
- package/build/dist/Types/SafeHtml.js.map +1 -0
- package/build/dist/Types/StatusPage/IncidentCreatedRenotify.js +117 -0
- package/build/dist/Types/StatusPage/IncidentCreatedRenotify.js.map +1 -0
- package/build/dist/Types/StatusPage/IncidentCreatedResend.js +86 -0
- package/build/dist/Types/StatusPage/IncidentCreatedResend.js.map +1 -0
- package/build/dist/Types/StatusPage/IncidentScopeAddedPagesNotification.js +246 -0
- package/build/dist/Types/StatusPage/IncidentScopeAddedPagesNotification.js.map +1 -0
- package/build/dist/Types/StatusPage/IncidentSubscriberAudience.js +183 -0
- package/build/dist/Types/StatusPage/IncidentSubscriberAudience.js.map +1 -0
- package/build/dist/Types/StatusPage/StatusPageSubscriberUnsubscribe.js +277 -0
- package/build/dist/Types/StatusPage/StatusPageSubscriberUnsubscribe.js.map +1 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationInterruption.js +27 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationInterruption.js.map +1 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationPreview.js +190 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationPreview.js.map +1 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationResend.js +102 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationResend.js.map +1 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationTemplateCompiler.js +97 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationTemplateCompiler.js.map +1 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationTemplatePreview.js +175 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationTemplatePreview.js.map +1 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationTemplateVariables.js +144 -4
- package/build/dist/Types/StatusPage/SubscriberNotificationTemplateVariables.js.map +1 -1
- package/build/dist/Types/StatusPage/SubscriberUpdateNotification.js +29 -9
- package/build/dist/Types/StatusPage/SubscriberUpdateNotification.js.map +1 -1
- package/build/dist/Types/Telemetry/InventoryLiveness.js +23 -0
- package/build/dist/Types/Telemetry/InventoryLiveness.js.map +1 -1
- package/build/dist/Types/Topology/TopologyApi.js +55 -0
- package/build/dist/Types/Topology/TopologyApi.js.map +1 -0
- package/build/dist/Types/Topology/TopologyTypeRules.js +165 -0
- package/build/dist/Types/Topology/TopologyTypeRules.js.map +1 -0
- package/build/dist/UI/Components/CustomFields/CustomFieldFormFields.js +169 -0
- package/build/dist/UI/Components/CustomFields/CustomFieldFormFields.js.map +1 -0
- package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js +212 -0
- package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js.map +1 -0
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +64 -49
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
- package/build/dist/UI/Components/Forms/ModelForm.js +39 -5
- package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
- package/build/dist/UI/Components/Forms/Validation.js +1 -1
- package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
- package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/AnalyticsModelTable.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +2 -2
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +13 -1
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/ModelTable.js +1 -1
- package/build/dist/UI/Components/ModelTable/ModelTable.js.map +1 -1
- package/build/dist/UI/Utils/SensitiveUrlToken.js +10 -5
- package/build/dist/UI/Utils/SensitiveUrlToken.js.map +1 -1
- package/build/dist/Utils/API.js +177 -1
- package/build/dist/Utils/API.js.map +1 -1
- package/build/dist/Utils/Incident/IncidentFromAlerts.js +38 -0
- package/build/dist/Utils/Incident/IncidentFromAlerts.js.map +1 -1
- package/build/dist/Utils/Incident/IncidentNoteTemplateVariables.js +155 -0
- package/build/dist/Utils/Incident/IncidentNoteTemplateVariables.js.map +1 -0
- package/build/dist/Utils/Markdown/MarkdownEscape.js +48 -0
- package/build/dist/Utils/Markdown/MarkdownEscape.js.map +1 -1
- package/build/dist/Utils/ProbeNetworkFailureUtil.js +460 -0
- package/build/dist/Utils/ProbeNetworkFailureUtil.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import dns from "dns";
|
|
2
2
|
import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
|
|
3
|
-
import { IsBillingEnabled } from "../../../../Server/EnvironmentConfig";
|
|
4
3
|
import DataSourceEgressGuard, {
|
|
5
4
|
AddressVerdict,
|
|
6
5
|
DEFAULT_DNS_RESOLVE_ATTEMPTS,
|
|
@@ -17,12 +16,22 @@ import EgressGuardException, {
|
|
|
17
16
|
} from "../../../../Types/Exception/EgressGuardException";
|
|
18
17
|
|
|
19
18
|
const ENV_VAR_NAME: string = "DATA_SOURCE_BLOCK_PRIVATE_ADDRESSES";
|
|
19
|
+
const BILLING_ENV_VAR_NAME: string = "BILLING_ENABLED";
|
|
20
20
|
|
|
21
21
|
let savedEnvValue: string | undefined = undefined;
|
|
22
|
+
let savedBillingValue: string | undefined = undefined;
|
|
22
23
|
|
|
24
|
+
/*
|
|
25
|
+
* Every test starts on a self-hosted install. Both variables decide how much a
|
|
26
|
+
* refusal is allowed to say, and CI exports BILLING_ENABLED=true, so a test
|
|
27
|
+
* that inherited the environment would assert a different contract there than
|
|
28
|
+
* on a laptop. Tests about SaaS set BILLING_ENABLED themselves.
|
|
29
|
+
*/
|
|
23
30
|
beforeEach(() => {
|
|
24
31
|
savedEnvValue = process.env[ENV_VAR_NAME];
|
|
32
|
+
savedBillingValue = process.env[BILLING_ENV_VAR_NAME];
|
|
25
33
|
delete process.env[ENV_VAR_NAME];
|
|
34
|
+
delete process.env[BILLING_ENV_VAR_NAME];
|
|
26
35
|
});
|
|
27
36
|
|
|
28
37
|
afterEach(() => {
|
|
@@ -31,6 +40,11 @@ afterEach(() => {
|
|
|
31
40
|
} else {
|
|
32
41
|
process.env[ENV_VAR_NAME] = savedEnvValue;
|
|
33
42
|
}
|
|
43
|
+
if (savedBillingValue === undefined) {
|
|
44
|
+
delete process.env[BILLING_ENV_VAR_NAME];
|
|
45
|
+
} else {
|
|
46
|
+
process.env[BILLING_ENV_VAR_NAME] = savedBillingValue;
|
|
47
|
+
}
|
|
34
48
|
jest.restoreAllMocks();
|
|
35
49
|
});
|
|
36
50
|
|
|
@@ -905,6 +919,33 @@ describe("DataSourceEgressGuard.assertHostnameAllowed - numeric hostname encodin
|
|
|
905
919
|
}
|
|
906
920
|
});
|
|
907
921
|
|
|
922
|
+
describe("DataSourceEgressGuard.shouldIncludeResolutionDetail", () => {
|
|
923
|
+
test("defaults to detail on a self-hosted install", () => {
|
|
924
|
+
expect(DataSourceEgressGuard.shouldIncludeResolutionDetail()).toBe(true);
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
test("defaults to no detail on SaaS", () => {
|
|
928
|
+
process.env[BILLING_ENV_VAR_NAME] = "true";
|
|
929
|
+
expect(DataSourceEgressGuard.shouldIncludeResolutionDetail()).toBe(false);
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
test("defaults to no detail once an operator forces SaaS policy", () => {
|
|
933
|
+
process.env[ENV_VAR_NAME] = "true";
|
|
934
|
+
expect(DataSourceEgressGuard.shouldIncludeResolutionDetail()).toBe(false);
|
|
935
|
+
});
|
|
936
|
+
|
|
937
|
+
test("an explicit value wins in either deployment", () => {
|
|
938
|
+
expect(DataSourceEgressGuard.shouldIncludeResolutionDetail(false)).toBe(
|
|
939
|
+
false,
|
|
940
|
+
);
|
|
941
|
+
|
|
942
|
+
process.env[BILLING_ENV_VAR_NAME] = "true";
|
|
943
|
+
expect(DataSourceEgressGuard.shouldIncludeResolutionDetail(true)).toBe(
|
|
944
|
+
true,
|
|
945
|
+
);
|
|
946
|
+
});
|
|
947
|
+
});
|
|
948
|
+
|
|
908
949
|
describe("DataSourceEgressGuard.shouldBlockPrivateAddresses", () => {
|
|
909
950
|
test("returns true when DATA_SOURCE_BLOCK_PRIVATE_ADDRESSES=true", () => {
|
|
910
951
|
process.env[ENV_VAR_NAME] = "true";
|
|
@@ -913,16 +954,18 @@ describe("DataSourceEgressGuard.shouldBlockPrivateAddresses", () => {
|
|
|
913
954
|
|
|
914
955
|
test("falls back to the billing flag when the env var is unset", () => {
|
|
915
956
|
delete process.env[ENV_VAR_NAME];
|
|
916
|
-
expect(DataSourceEgressGuard.shouldBlockPrivateAddresses()).toBe(
|
|
917
|
-
|
|
918
|
-
|
|
957
|
+
expect(DataSourceEgressGuard.shouldBlockPrivateAddresses()).toBe(false);
|
|
958
|
+
|
|
959
|
+
process.env[BILLING_ENV_VAR_NAME] = "true";
|
|
960
|
+
expect(DataSourceEgressGuard.shouldBlockPrivateAddresses()).toBe(true);
|
|
919
961
|
});
|
|
920
962
|
|
|
921
963
|
test("treats any value other than 'true' as unset", () => {
|
|
922
964
|
process.env[ENV_VAR_NAME] = "false";
|
|
923
|
-
expect(DataSourceEgressGuard.shouldBlockPrivateAddresses()).toBe(
|
|
924
|
-
|
|
925
|
-
|
|
965
|
+
expect(DataSourceEgressGuard.shouldBlockPrivateAddresses()).toBe(false);
|
|
966
|
+
|
|
967
|
+
process.env[BILLING_ENV_VAR_NAME] = "true";
|
|
968
|
+
expect(DataSourceEgressGuard.shouldBlockPrivateAddresses()).toBe(true);
|
|
926
969
|
});
|
|
927
970
|
|
|
928
971
|
test("checkAddress derives the policy from the env var when options omit the flag", () => {
|
|
@@ -1174,7 +1217,7 @@ describe("DataSourceEgressGuard - typed failure reasons", () => {
|
|
|
1174
1217
|
);
|
|
1175
1218
|
|
|
1176
1219
|
test.each([undefined, true])(
|
|
1177
|
-
"a resolver rejection is ResolutionFailed when includeResolvedAddressInError is %s",
|
|
1220
|
+
"a resolver rejection is ResolutionFailed on a self-hosted install when includeResolvedAddressInError is %s",
|
|
1178
1221
|
async (includeResolvedAddressInError: boolean | undefined) => {
|
|
1179
1222
|
const error: Error = await captureRejection(
|
|
1180
1223
|
DataSourceEgressGuard.assertHostnameAllowed("missing.example.com", {
|
|
@@ -1195,7 +1238,7 @@ describe("DataSourceEgressGuard - typed failure reasons", () => {
|
|
|
1195
1238
|
);
|
|
1196
1239
|
|
|
1197
1240
|
test.each([undefined, true])(
|
|
1198
|
-
"an empty DNS answer is ResolutionFailed when includeResolvedAddressInError is %s",
|
|
1241
|
+
"an empty DNS answer is ResolutionFailed on a self-hosted install when includeResolvedAddressInError is %s",
|
|
1199
1242
|
async (includeResolvedAddressInError: boolean | undefined) => {
|
|
1200
1243
|
const resolver: Resolver = makeResolver([]);
|
|
1201
1244
|
const error: Error = await captureRejection(
|
|
@@ -1217,7 +1260,7 @@ describe("DataSourceEgressGuard - typed failure reasons", () => {
|
|
|
1217
1260
|
);
|
|
1218
1261
|
|
|
1219
1262
|
test.each([undefined, true])(
|
|
1220
|
-
"a blocked resolved address is AddressBlocked when includeResolvedAddressInError is %s",
|
|
1263
|
+
"a blocked resolved address is AddressBlocked on a self-hosted install when includeResolvedAddressInError is %s",
|
|
1221
1264
|
async (includeResolvedAddressInError: boolean | undefined) => {
|
|
1222
1265
|
const resolver: Resolver = makeResolver([
|
|
1223
1266
|
{ address: "10.23.45.67", family: 4 },
|
|
@@ -1542,6 +1585,199 @@ describe("DataSourceEgressGuard - the sanitized failure is one indistinguishable
|
|
|
1542
1585
|
expect(line).toContain(ORACLE_HOSTNAME);
|
|
1543
1586
|
}
|
|
1544
1587
|
});
|
|
1588
|
+
|
|
1589
|
+
/*
|
|
1590
|
+
* Most callers never set includeResolvedAddressInError: the workflow Send
|
|
1591
|
+
* Email step, a project's SMTP server, OAuth token URLs, LLM providers,
|
|
1592
|
+
* data sources, Runbook HTTP steps. Each echoes the guard's message to
|
|
1593
|
+
* project members, so on OneUptime Cloud the default itself has to be the
|
|
1594
|
+
* sanitized path — a member typing `redis` or
|
|
1595
|
+
* `kubernetes.default.svc.cluster.local` into any of those fields must not
|
|
1596
|
+
* learn whether the name exists or what it points at.
|
|
1597
|
+
*/
|
|
1598
|
+
describe("when the caller leaves includeResolvedAddressInError unset", () => {
|
|
1599
|
+
const callerOptions: EgressGuardOptions = { targetLabel: "SMTP server" };
|
|
1600
|
+
|
|
1601
|
+
const resolverReturnsLoopback: EgressResolveFunction = makeResolver([
|
|
1602
|
+
{ address: "127.0.0.1", family: 4 },
|
|
1603
|
+
]).resolveFunction;
|
|
1604
|
+
|
|
1605
|
+
test.each([
|
|
1606
|
+
["on SaaS (BILLING_ENABLED=true)", BILLING_ENV_VAR_NAME],
|
|
1607
|
+
[
|
|
1608
|
+
"once an operator forces SaaS policy (DATA_SOURCE_BLOCK_PRIVATE_ADDRESSES=true)",
|
|
1609
|
+
ENV_VAR_NAME,
|
|
1610
|
+
],
|
|
1611
|
+
])(
|
|
1612
|
+
"%s a DNS failure and a blocked resolution are indistinguishable",
|
|
1613
|
+
async (_deployment: string, envVarName: string) => {
|
|
1614
|
+
process.env[envVarName] = "true";
|
|
1615
|
+
|
|
1616
|
+
const errors: Array<Error> = [
|
|
1617
|
+
await failWith(resolverRejects, callerOptions),
|
|
1618
|
+
await failWith(resolverReturnsNothing, callerOptions),
|
|
1619
|
+
await failWith(resolverReturnsPrivateAddress, callerOptions),
|
|
1620
|
+
await failWith(resolverReturnsLoopback, callerOptions),
|
|
1621
|
+
];
|
|
1622
|
+
|
|
1623
|
+
const expectedFingerprint: EgressFailureFingerprint = fingerprint(
|
|
1624
|
+
errors[0]!,
|
|
1625
|
+
);
|
|
1626
|
+
expect(expectedFingerprint.message).toBe(
|
|
1627
|
+
`SMTP server host ${ORACLE_HOSTNAME} could not be reached.`,
|
|
1628
|
+
);
|
|
1629
|
+
expect(expectedFingerprint.reason).toBe(
|
|
1630
|
+
EgressFailureReason.Unreachable,
|
|
1631
|
+
);
|
|
1632
|
+
|
|
1633
|
+
for (const error of errors) {
|
|
1634
|
+
expect(fingerprint(error)).toEqual(expectedFingerprint);
|
|
1635
|
+
expect(Object.keys(error).sort()).toEqual(["_code", "reason"]);
|
|
1636
|
+
expect(JSON.stringify(error)).not.toContain(SECRET_ADDRESS);
|
|
1637
|
+
expect(error.message).not.toContain("127.0.0.1");
|
|
1638
|
+
expect(error.message).not.toContain("ENOTFOUND");
|
|
1639
|
+
}
|
|
1640
|
+
},
|
|
1641
|
+
);
|
|
1642
|
+
|
|
1643
|
+
test("on SaaS the URL entry points are sanitized too", async () => {
|
|
1644
|
+
/*
|
|
1645
|
+
* LLM providers, OAuth token URLs, Runbook HTTP steps and domain
|
|
1646
|
+
* verification come in through the URL forms, not the hostname one.
|
|
1647
|
+
*/
|
|
1648
|
+
process.env[BILLING_ENV_VAR_NAME] = "true";
|
|
1649
|
+
|
|
1650
|
+
const missingError: Error = await captureRejection(
|
|
1651
|
+
DataSourceEgressGuard.assertUrlAllowedAndPin(
|
|
1652
|
+
`https://${ORACLE_HOSTNAME}/v1/chat/completions`,
|
|
1653
|
+
{ targetLabel: "LLM provider", resolveFunction: resolverRejects },
|
|
1654
|
+
),
|
|
1655
|
+
);
|
|
1656
|
+
const internalError: Error = await captureRejection(
|
|
1657
|
+
DataSourceEgressGuard.assertUrlAllowed(
|
|
1658
|
+
`https://${ORACLE_HOSTNAME}/token`,
|
|
1659
|
+
{
|
|
1660
|
+
targetLabel: "LLM provider",
|
|
1661
|
+
resolveFunction: resolverReturnsPrivateAddress,
|
|
1662
|
+
},
|
|
1663
|
+
),
|
|
1664
|
+
);
|
|
1665
|
+
|
|
1666
|
+
expect(fingerprint(internalError)).toEqual(fingerprint(missingError));
|
|
1667
|
+
expect(missingError.message).toBe(
|
|
1668
|
+
`LLM provider host ${ORACLE_HOSTNAME} could not be reached.`,
|
|
1669
|
+
);
|
|
1670
|
+
});
|
|
1671
|
+
|
|
1672
|
+
test("on SaaS a caller that loosens blockPrivateAddresses still gets no detail", async () => {
|
|
1673
|
+
/*
|
|
1674
|
+
* The default follows the deployment, not the per-call address policy:
|
|
1675
|
+
* who typed the target is what makes the detail an oracle.
|
|
1676
|
+
*/
|
|
1677
|
+
process.env[BILLING_ENV_VAR_NAME] = "true";
|
|
1678
|
+
const loosenedOptions: EgressGuardOptions = {
|
|
1679
|
+
...callerOptions,
|
|
1680
|
+
blockPrivateAddresses: false,
|
|
1681
|
+
};
|
|
1682
|
+
|
|
1683
|
+
const missingError: Error = await failWith(
|
|
1684
|
+
resolverRejects,
|
|
1685
|
+
loosenedOptions,
|
|
1686
|
+
);
|
|
1687
|
+
const loopbackError: Error = await failWith(
|
|
1688
|
+
resolverReturnsLoopback,
|
|
1689
|
+
loosenedOptions,
|
|
1690
|
+
);
|
|
1691
|
+
|
|
1692
|
+
expect(fingerprint(loopbackError)).toEqual(fingerprint(missingError));
|
|
1693
|
+
expect(loopbackError.message).toBe(
|
|
1694
|
+
`SMTP server host ${ORACLE_HOSTNAME} could not be reached.`,
|
|
1695
|
+
);
|
|
1696
|
+
});
|
|
1697
|
+
|
|
1698
|
+
test("on SaaS an explicit true still gets the detail", async () => {
|
|
1699
|
+
process.env[BILLING_ENV_VAR_NAME] = "true";
|
|
1700
|
+
const detailedOptions: EgressGuardOptions = {
|
|
1701
|
+
...callerOptions,
|
|
1702
|
+
includeResolvedAddressInError: true,
|
|
1703
|
+
};
|
|
1704
|
+
|
|
1705
|
+
const missingError: Error = await failWith(
|
|
1706
|
+
resolverRejects,
|
|
1707
|
+
detailedOptions,
|
|
1708
|
+
);
|
|
1709
|
+
const internalError: Error = await failWith(
|
|
1710
|
+
resolverReturnsPrivateAddress,
|
|
1711
|
+
detailedOptions,
|
|
1712
|
+
);
|
|
1713
|
+
|
|
1714
|
+
expectEgressFailure(missingError, EgressFailureReason.ResolutionFailed);
|
|
1715
|
+
expectEgressFailure(internalError, EgressFailureReason.AddressBlocked);
|
|
1716
|
+
expect(internalError.message).toBe(
|
|
1717
|
+
`SMTP server host ${ORACLE_HOSTNAME} resolves to ${SECRET_ADDRESS}, which is not allowed: private network address.`,
|
|
1718
|
+
);
|
|
1719
|
+
});
|
|
1720
|
+
|
|
1721
|
+
test("on SaaS a literal IP is still refused with its reason", async () => {
|
|
1722
|
+
// It restates what the member typed and reveals nothing about DNS.
|
|
1723
|
+
process.env[BILLING_ENV_VAR_NAME] = "true";
|
|
1724
|
+
|
|
1725
|
+
const error: Error = await captureRejection(
|
|
1726
|
+
DataSourceEgressGuard.assertHostnameAllowed(SECRET_ADDRESS, {
|
|
1727
|
+
...callerOptions,
|
|
1728
|
+
}),
|
|
1729
|
+
);
|
|
1730
|
+
|
|
1731
|
+
expectEgressFailure(error, EgressFailureReason.AddressBlocked);
|
|
1732
|
+
expect(error.message).toBe(
|
|
1733
|
+
`SMTP server host ${SECRET_ADDRESS} is not allowed: private network address.`,
|
|
1734
|
+
);
|
|
1735
|
+
});
|
|
1736
|
+
|
|
1737
|
+
test("on SaaS the operator still gets the precise cause in the debug log", async () => {
|
|
1738
|
+
process.env[BILLING_ENV_VAR_NAME] = "true";
|
|
1739
|
+
const debugSpy: jest.SpyInstance = jest
|
|
1740
|
+
.spyOn(logger, "debug")
|
|
1741
|
+
.mockImplementation((): void => {
|
|
1742
|
+
// Swallow the output; only the call itself is under test.
|
|
1743
|
+
});
|
|
1744
|
+
|
|
1745
|
+
await failWith(resolverRejects, callerOptions);
|
|
1746
|
+
await failWith(resolverReturnsNothing, callerOptions);
|
|
1747
|
+
await failWith(resolverReturnsPrivateAddress, callerOptions);
|
|
1748
|
+
|
|
1749
|
+
const loggedLines: Array<string> = debugSpy.mock.calls.map(
|
|
1750
|
+
(call: Array<unknown>) => {
|
|
1751
|
+
return String(call[0]);
|
|
1752
|
+
},
|
|
1753
|
+
);
|
|
1754
|
+
|
|
1755
|
+
expect(loggedLines).toHaveLength(3);
|
|
1756
|
+
expect(loggedLines[0]).toContain("ENOTFOUND");
|
|
1757
|
+
expect(loggedLines[1]).toContain("no addresses");
|
|
1758
|
+
expect(loggedLines[2]).toContain(SECRET_ADDRESS);
|
|
1759
|
+
expect(loggedLines[2]).toContain("private network address");
|
|
1760
|
+
});
|
|
1761
|
+
|
|
1762
|
+
test("a self-hosted install keeps the operator-friendly detail", async () => {
|
|
1763
|
+
// Private ranges are reachable here, so loopback is the refused case.
|
|
1764
|
+
const missingError: Error = await failWith(
|
|
1765
|
+
resolverRejects,
|
|
1766
|
+
callerOptions,
|
|
1767
|
+
);
|
|
1768
|
+
const loopbackError: Error = await failWith(
|
|
1769
|
+
resolverReturnsLoopback,
|
|
1770
|
+
callerOptions,
|
|
1771
|
+
);
|
|
1772
|
+
|
|
1773
|
+
expect(missingError.message).toBe(
|
|
1774
|
+
`Could not resolve smtp server host ${ORACLE_HOSTNAME}: getaddrinfo ENOTFOUND ${ORACLE_HOSTNAME} ${SECRET_ADDRESS}`,
|
|
1775
|
+
);
|
|
1776
|
+
expect(loopbackError.message).toBe(
|
|
1777
|
+
`SMTP server host ${ORACLE_HOSTNAME} resolves to 127.0.0.1, which is not allowed: loopback address.`,
|
|
1778
|
+
);
|
|
1779
|
+
});
|
|
1780
|
+
});
|
|
1545
1781
|
});
|
|
1546
1782
|
|
|
1547
1783
|
/*
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import yaml from "js-yaml";
|
|
5
|
+
import DataSourceEgressGuard, {
|
|
6
|
+
ResolvedAddress,
|
|
7
|
+
} from "../../../../Server/Utils/DataSource/EgressGuard";
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
* DATA_SOURCE_BLOCK_PRIVATE_ADDRESSES lets a self-hosted operator make the
|
|
11
|
+
* egress guard refuse private ranges for data sources, LLM providers, SMTP,
|
|
12
|
+
* OAuth token URLs, OIDC discovery and runbook HTTP steps. The guard has read
|
|
13
|
+
* it for a long time, and the docs told operators to set it, but Docker
|
|
14
|
+
* Compose never passed it to the app container and config.example.env had no
|
|
15
|
+
* line for it -- so setting it in config.env did nothing.
|
|
16
|
+
*
|
|
17
|
+
* This pins the Compose half of the wiring (the Helm half is covered by
|
|
18
|
+
* HelmChart/Public/oneuptime/tests/outbound-connections-egress_test.yaml) and
|
|
19
|
+
* ties it to the name the guard actually reads, by feeding the interpolated
|
|
20
|
+
* value to the real guard rather than comparing strings.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const ENV_VAR_NAME: string = "DATA_SOURCE_BLOCK_PRIVATE_ADDRESSES";
|
|
24
|
+
// The webhook half of the egress policy, already wired end to end.
|
|
25
|
+
const SIBLING_ENV_VAR_NAME: string = "ALLOW_PRIVATE_NETWORK_WEBHOOKS";
|
|
26
|
+
|
|
27
|
+
const REPO_ROOT: string = path.resolve(__dirname, "../../../../../..");
|
|
28
|
+
const COMPOSE_PATH: string = path.join(REPO_ROOT, "docker-compose.base.yml");
|
|
29
|
+
const EXAMPLE_ENV_PATH: string = path.join(REPO_ROOT, "config.example.env");
|
|
30
|
+
|
|
31
|
+
type ComposeEnvironment = Record<string, string | number | boolean | null>;
|
|
32
|
+
|
|
33
|
+
interface ComposeService {
|
|
34
|
+
environment?: ComposeEnvironment;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface ComposeConfig {
|
|
38
|
+
"x-common-variables": ComposeEnvironment;
|
|
39
|
+
services: Record<string, ComposeService>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// js-yaml resolves the `<<: *anchor` merge keys, as Compose does.
|
|
43
|
+
const loadCompose: () => ComposeConfig = (): ComposeConfig => {
|
|
44
|
+
return yaml.load(fs.readFileSync(COMPOSE_PATH, "utf8")) as ComposeConfig;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const exampleEnvLines: (name: string) => Array<string> = (
|
|
48
|
+
name: string,
|
|
49
|
+
): Array<string> => {
|
|
50
|
+
const source: string = fs.readFileSync(EXAMPLE_ENV_PATH, "utf8");
|
|
51
|
+
return source.split("\n").filter((line: string) => {
|
|
52
|
+
return line.startsWith(`${name}=`);
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/*
|
|
57
|
+
* Resolve a Compose `${NAME:-default}` reference the way Compose does: the
|
|
58
|
+
* default applies when the variable is unset OR empty. config.env is exported
|
|
59
|
+
* into the shell before `docker compose up`, so `shellEnv` stands in for it.
|
|
60
|
+
*/
|
|
61
|
+
const interpolate: (
|
|
62
|
+
reference: string,
|
|
63
|
+
shellEnv: Record<string, string | undefined>,
|
|
64
|
+
) => string = (
|
|
65
|
+
reference: string,
|
|
66
|
+
shellEnv: Record<string, string | undefined>,
|
|
67
|
+
): string => {
|
|
68
|
+
const match: RegExpMatchArray | null = reference.match(
|
|
69
|
+
/^\$\{([A-Z0-9_]+):-(.*)\}$/,
|
|
70
|
+
);
|
|
71
|
+
if (!match) {
|
|
72
|
+
throw new Error(`Not a defaulted Compose reference: ${reference}`);
|
|
73
|
+
}
|
|
74
|
+
const value: string | undefined = shellEnv[match[1]!];
|
|
75
|
+
return value ? value : match[2]!;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const appReference: () => string = (): string => {
|
|
79
|
+
const reference: unknown =
|
|
80
|
+
loadCompose().services["app"]?.environment?.[ENV_VAR_NAME];
|
|
81
|
+
expect(typeof reference).toBe("string");
|
|
82
|
+
return reference as string;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
describe("DATA_SOURCE_BLOCK_PRIVATE_ADDRESSES deployment wiring", () => {
|
|
86
|
+
let savedValue: string | undefined = undefined;
|
|
87
|
+
let savedBilling: string | undefined = undefined;
|
|
88
|
+
|
|
89
|
+
beforeEach(() => {
|
|
90
|
+
savedValue = process.env[ENV_VAR_NAME];
|
|
91
|
+
savedBilling = process.env["BILLING_ENABLED"];
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
afterEach(() => {
|
|
95
|
+
for (const [name, saved] of [
|
|
96
|
+
[ENV_VAR_NAME, savedValue],
|
|
97
|
+
["BILLING_ENABLED", savedBilling],
|
|
98
|
+
] as Array<[string, string | undefined]>) {
|
|
99
|
+
if (saved === undefined) {
|
|
100
|
+
delete process.env[name];
|
|
101
|
+
} else {
|
|
102
|
+
process.env[name] = saved;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("Docker Compose passes it in the shared environment, defaulted off", () => {
|
|
108
|
+
expect(loadCompose()["x-common-variables"][ENV_VAR_NAME]).toBe(
|
|
109
|
+
`\${${ENV_VAR_NAME}:-false}`,
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
/*
|
|
114
|
+
* The app container runs the API, the queue workers, workflows and the
|
|
115
|
+
* notification (SMTP) code, which is every server-side caller of the guard.
|
|
116
|
+
*/
|
|
117
|
+
test("the app container receives it", () => {
|
|
118
|
+
expect(appReference()).toBe(`\${${ENV_VAR_NAME}:-false}`);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("every Compose service that gets the webhook policy gets this one too", () => {
|
|
122
|
+
const services: Record<string, ComposeService> = loadCompose().services;
|
|
123
|
+
const withWebhookPolicy: Array<string> = Object.keys(services).filter(
|
|
124
|
+
(name: string) => {
|
|
125
|
+
return (
|
|
126
|
+
services[name]?.environment?.[SIBLING_ENV_VAR_NAME] !== undefined
|
|
127
|
+
);
|
|
128
|
+
},
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
expect(withWebhookPolicy).toContain("app");
|
|
132
|
+
for (const name of withWebhookPolicy) {
|
|
133
|
+
expect({
|
|
134
|
+
service: name,
|
|
135
|
+
value: services[name]?.environment?.[ENV_VAR_NAME],
|
|
136
|
+
}).toEqual({ service: name, value: `\${${ENV_VAR_NAME}:-false}` });
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
/*
|
|
141
|
+
* One line, so an operator flips it instead of appending a second one (the
|
|
142
|
+
* later line would win), and MergeEnvTemplate adds it to an existing
|
|
143
|
+
* config.env on upgrade.
|
|
144
|
+
*/
|
|
145
|
+
test("config.example.env ships exactly one line, set to false", () => {
|
|
146
|
+
expect(exampleEnvLines(ENV_VAR_NAME)).toEqual([`${ENV_VAR_NAME}=false`]);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("a self-hosted install keeps private ranges reachable with the shipped line", () => {
|
|
150
|
+
const shipped: string = exampleEnvLines(ENV_VAR_NAME)[0]!.split("=")[1]!;
|
|
151
|
+
process.env["BILLING_ENABLED"] = "false";
|
|
152
|
+
process.env[ENV_VAR_NAME] = interpolate(appReference(), {
|
|
153
|
+
[ENV_VAR_NAME]: shipped,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
expect(DataSourceEgressGuard.shouldBlockPrivateAddresses()).toBe(false);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("a config.env written before the line existed keeps private ranges reachable", () => {
|
|
160
|
+
process.env["BILLING_ENABLED"] = "false";
|
|
161
|
+
process.env[ENV_VAR_NAME] = interpolate(appReference(), {});
|
|
162
|
+
|
|
163
|
+
expect(process.env[ENV_VAR_NAME]).toBe("false");
|
|
164
|
+
expect(DataSourceEgressGuard.shouldBlockPrivateAddresses()).toBe(false);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// The regression itself: the operator's value has to reach the guard.
|
|
168
|
+
test("setting it to true in config.env makes the guard block private ranges", () => {
|
|
169
|
+
process.env["BILLING_ENABLED"] = "false";
|
|
170
|
+
process.env[ENV_VAR_NAME] = interpolate(appReference(), {
|
|
171
|
+
[ENV_VAR_NAME]: "true",
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
expect(DataSourceEgressGuard.shouldBlockPrivateAddresses()).toBe(true);
|
|
175
|
+
expect(DataSourceEgressGuard.checkAddress("10.0.0.5").blocked).toBe(true);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
/*
|
|
179
|
+
* The self-hosted docs quote the refusal an operator sees once the switch is
|
|
180
|
+
* on, so they can match it against a log. Produce it with the real guard,
|
|
181
|
+
* the way LLMService calls it, rather than trusting the copy.
|
|
182
|
+
*/
|
|
183
|
+
test("the docs quote the refusal the guard actually reports", async () => {
|
|
184
|
+
const docs: string = fs.readFileSync(
|
|
185
|
+
path.join(
|
|
186
|
+
REPO_ROOT,
|
|
187
|
+
"packages/App/FeatureSet/Docs/Content/en/self-hosted/private-network-access.md",
|
|
188
|
+
),
|
|
189
|
+
"utf8",
|
|
190
|
+
);
|
|
191
|
+
// The refused name first, then the same address typed as an IP.
|
|
192
|
+
const quotes: Array<string> = Array.from(
|
|
193
|
+
docs.matchAll(/_"(LLM provider host [^"]+)"_/g),
|
|
194
|
+
(match: RegExpMatchArray) => {
|
|
195
|
+
return match[1]!;
|
|
196
|
+
},
|
|
197
|
+
);
|
|
198
|
+
expect(quotes).toHaveLength(2);
|
|
199
|
+
|
|
200
|
+
process.env["BILLING_ENABLED"] = "false";
|
|
201
|
+
process.env[ENV_VAR_NAME] = "true";
|
|
202
|
+
|
|
203
|
+
const refusalFor: (url: string) => Promise<string> = async (
|
|
204
|
+
url: string,
|
|
205
|
+
): Promise<string> => {
|
|
206
|
+
try {
|
|
207
|
+
await DataSourceEgressGuard.assertUrlAllowedAndPin(url, {
|
|
208
|
+
targetLabel: "LLM provider",
|
|
209
|
+
resolveFunction: async (): Promise<Array<ResolvedAddress>> => {
|
|
210
|
+
return [{ address: "10.0.4.12", family: 4 }];
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
} catch (err) {
|
|
214
|
+
return (err as Error).message;
|
|
215
|
+
}
|
|
216
|
+
return "";
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
expect(
|
|
220
|
+
await refusalFor("http://ollama.internal:11434/v1/chat/completions"),
|
|
221
|
+
).toBe(quotes[0]);
|
|
222
|
+
expect(await refusalFor("http://10.0.4.12:11434/v1/chat/completions")).toBe(
|
|
223
|
+
quotes[1],
|
|
224
|
+
);
|
|
225
|
+
});
|
|
226
|
+
});
|
|
@@ -6,6 +6,9 @@ import DataSourceEgressGuard, {
|
|
|
6
6
|
} from "../../../../Server/Utils/DataSource/EgressGuard";
|
|
7
7
|
import BadDataException from "../../../../Types/Exception/BadDataException";
|
|
8
8
|
import { describe, expect, test } from "@jest/globals";
|
|
9
|
+
import { ClientRequest } from "http";
|
|
10
|
+
import https from "https";
|
|
11
|
+
import { startEachTestOnSelfHostedEgressPolicy } from "../EgressPolicyEnvironment";
|
|
9
12
|
|
|
10
13
|
/*
|
|
11
14
|
* The egress guard is only half a control on its own: validating an address
|
|
@@ -61,6 +64,9 @@ const callLookup: CallLookup = (
|
|
|
61
64
|
});
|
|
62
65
|
};
|
|
63
66
|
|
|
67
|
+
// Detailed refusals are asserted throughout; SaaS behaviour is tested apart.
|
|
68
|
+
startEachTestOnSelfHostedEgressPolicy();
|
|
69
|
+
|
|
64
70
|
describe("DataSourceEgressGuard.createPinnedLookup", () => {
|
|
65
71
|
const addresses: Array<ResolvedAddress> = [
|
|
66
72
|
{ address: "93.184.216.34", family: 4 },
|
|
@@ -113,6 +119,131 @@ describe("DataSourceEgressGuard.createPinnedLookup", () => {
|
|
|
113
119
|
|
|
114
120
|
expect(result.address).toBe("93.184.216.34");
|
|
115
121
|
});
|
|
122
|
+
|
|
123
|
+
/*
|
|
124
|
+
* dns.lookup never calls back synchronously, and Node's net/tls connect
|
|
125
|
+
* relies on that. See the regression test below for what broke.
|
|
126
|
+
*/
|
|
127
|
+
test.each([
|
|
128
|
+
["every address", { all: true }],
|
|
129
|
+
["the first address", { all: false }],
|
|
130
|
+
["no address of the wanted family", { family: 6 as number }],
|
|
131
|
+
])(
|
|
132
|
+
"answers asynchronously with %s",
|
|
133
|
+
async (
|
|
134
|
+
_label: string,
|
|
135
|
+
options: { all?: boolean | undefined; family?: number | undefined },
|
|
136
|
+
) => {
|
|
137
|
+
const lookup: EgressLookupFunction =
|
|
138
|
+
DataSourceEgressGuard.createPinnedLookup([
|
|
139
|
+
{ address: "93.184.216.34", family: 4 },
|
|
140
|
+
]);
|
|
141
|
+
let hasAnswered: boolean = false;
|
|
142
|
+
|
|
143
|
+
const answered: Promise<LookupResult> = new Promise(
|
|
144
|
+
(resolve: (value: LookupResult) => void) => {
|
|
145
|
+
lookup(
|
|
146
|
+
"anything.example",
|
|
147
|
+
options,
|
|
148
|
+
(
|
|
149
|
+
error: NodeJS.ErrnoException | null,
|
|
150
|
+
address: string | Array<{ address: string; family: number }>,
|
|
151
|
+
family?: number,
|
|
152
|
+
) => {
|
|
153
|
+
hasAnswered = true;
|
|
154
|
+
resolve({ error, address, family });
|
|
155
|
+
},
|
|
156
|
+
);
|
|
157
|
+
},
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
expect(hasAnswered).toBe(false);
|
|
161
|
+
|
|
162
|
+
const result: LookupResult = await answered;
|
|
163
|
+
|
|
164
|
+
expect(hasAnswered).toBe(true);
|
|
165
|
+
if (options.family === 6) {
|
|
166
|
+
expect(result.error?.code).toBe("ENOTFOUND");
|
|
167
|
+
} else {
|
|
168
|
+
expect(result.error).toBeNull();
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
/*
|
|
175
|
+
* REGRESSION: an HTTPS request through a pinned agent whose every address
|
|
176
|
+
* fails at connect() time. That is what a probe with no IPv6 gets for an
|
|
177
|
+
* IPv6-only hostname (EADDRNOTAVAIL/ENETUNREACH from connect() itself), and
|
|
178
|
+
* with a lookup that answered synchronously Node destroyed the TLSSocket
|
|
179
|
+
* before tls.connect called setServername on it: https.get threw
|
|
180
|
+
* "Cannot read properties of null (reading 'setServername')", which the
|
|
181
|
+
* monitor reported instead of the real error, and the real error escaped as
|
|
182
|
+
* an unhandled 'error' event on the socket.
|
|
183
|
+
*
|
|
184
|
+
* connect() to the broadcast address fails the same way, locally and with no
|
|
185
|
+
* packet sent (EAFNOSUPPORT on macOS, ENETUNREACH on Linux).
|
|
186
|
+
*/
|
|
187
|
+
describe("DataSourceEgressGuard.createPinnedLookup through an https agent", () => {
|
|
188
|
+
test("a connect that fails on every pinned address rejects cleanly instead of throwing", async () => {
|
|
189
|
+
const uncaught: Array<unknown> = [];
|
|
190
|
+
const onUncaught: (error: unknown) => void = (error: unknown): void => {
|
|
191
|
+
uncaught.push(error);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
process.on("uncaughtException", onUncaught);
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
const agent: https.Agent = new https.Agent({
|
|
198
|
+
lookup: DataSourceEgressGuard.createPinnedLookup([
|
|
199
|
+
{ address: "255.255.255.255", family: 4 },
|
|
200
|
+
]) as never,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const requestError: Error = await new Promise(
|
|
204
|
+
(resolve: (error: Error) => void, reject: (error: Error) => void) => {
|
|
205
|
+
let request: ClientRequest;
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
request = https.get("https://pinned.example/", {
|
|
209
|
+
agent: agent,
|
|
210
|
+
timeout: 5000,
|
|
211
|
+
});
|
|
212
|
+
} catch (error) {
|
|
213
|
+
reject(error as Error);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
request.on("error", (error: Error) => {
|
|
218
|
+
resolve(error);
|
|
219
|
+
});
|
|
220
|
+
request.on("timeout", () => {
|
|
221
|
+
request.destroy(new Error("request timed out"));
|
|
222
|
+
});
|
|
223
|
+
request.on("response", () => {
|
|
224
|
+
reject(new Error("broadcast address answered"));
|
|
225
|
+
});
|
|
226
|
+
},
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
// Give a stray 'error' event on the socket a chance to surface.
|
|
230
|
+
await new Promise<void>((resolve: () => void) => {
|
|
231
|
+
setTimeout(resolve, 50);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
expect(requestError).not.toBeInstanceOf(TypeError);
|
|
235
|
+
expect(requestError.message).not.toContain("setServername");
|
|
236
|
+
expect(
|
|
237
|
+
typeof (requestError as NodeJS.ErrnoException).code === "string" ||
|
|
238
|
+
Array.isArray((requestError as { errors?: unknown }).errors),
|
|
239
|
+
).toBe(true);
|
|
240
|
+
expect(uncaught).toEqual([]);
|
|
241
|
+
|
|
242
|
+
agent.destroy();
|
|
243
|
+
} finally {
|
|
244
|
+
process.removeListener("uncaughtException", onUncaught);
|
|
245
|
+
}
|
|
246
|
+
});
|
|
116
247
|
});
|
|
117
248
|
|
|
118
249
|
describe("DataSourceEgressGuard.createPinnedAgents", () => {
|