@oneuptime/common 14.0.7 → 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/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/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 +14 -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/DatabaseService.ts +93 -10
- 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 +1535 -65
- package/Server/Services/IncidentStateTimelineService.ts +13 -0
- package/Server/Services/IncidentTemplateService.ts +109 -0
- package/Server/Services/ScheduledMaintenancePublicNoteService.ts +30 -1
- package/Server/Services/ScheduledMaintenanceService.ts +68 -18
- 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/Types/Markdown.ts +153 -6
- package/Server/Types/Workflow/Components/Email.ts +61 -2
- package/Server/Utils/CustomField/CustomFieldRename.ts +378 -0
- package/Server/Utils/DataSource/EgressGuard.ts +66 -21
- package/Server/Utils/Mail/PinnedSmtpSocket.ts +130 -0
- package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +10 -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/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/CustomFieldsSettingsIncidentOptions.test.tsx +357 -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 +242 -5
- package/Tests/App/Dashboard/IncidentChangeStateNotifyDefault.test.tsx +91 -1
- 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/PublicNotePagesNotifyDefault.test.tsx +113 -1
- 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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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 +14 -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/DatabaseService.js +56 -7
- package/build/dist/Server/Services/DatabaseService.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 +1052 -0
- 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/ScheduledMaintenancePublicNoteService.js +26 -1
- package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceService.js +47 -17
- 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/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/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/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/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/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/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/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/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
|
@@ -19,6 +19,7 @@ import DataSourceType from "../../../../Types/DataSource/DataSourceType";
|
|
|
19
19
|
import Dictionary from "../../../../Types/Dictionary";
|
|
20
20
|
import BadDataException from "../../../../Types/Exception/BadDataException";
|
|
21
21
|
import { beforeEach, describe, expect, test } from "@jest/globals";
|
|
22
|
+
import { startEachTestOnSelfHostedEgressPolicy } from "../EgressPolicyEnvironment";
|
|
22
23
|
|
|
23
24
|
/*
|
|
24
25
|
* Hermetic tests for the SSRF-hardened HTTP client. axios is mocked at the
|
|
@@ -160,6 +161,9 @@ function runLookup(
|
|
|
160
161
|
);
|
|
161
162
|
}
|
|
162
163
|
|
|
164
|
+
// Detailed refusals are asserted throughout; SaaS behaviour is tested apart.
|
|
165
|
+
startEachTestOnSelfHostedEgressPolicy();
|
|
166
|
+
|
|
163
167
|
beforeEach(() => {
|
|
164
168
|
axiosMock.mockReset();
|
|
165
169
|
axiosMock.mockResolvedValue({ status: 200, data: "{}" });
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { afterEach, beforeEach } from "@jest/globals";
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* How much an egress refusal may say depends on the deployment:
|
|
5
|
+
* DataSourceEgressGuard.shouldIncludeResolutionDetail() — which SSRFProtection
|
|
6
|
+
* shares — allows detail on a self-hosted install and none on SaaS
|
|
7
|
+
* (BILLING_ENABLED=true) or once DATA_SOURCE_BLOCK_PRIVATE_ADDRESSES=true.
|
|
8
|
+
*
|
|
9
|
+
* CI exports BILLING_ENABLED=true, so a suite that asserts a detailed refusal
|
|
10
|
+
* ("resolves to 127.0.0.1", "could not be resolved via DNS") without pinning
|
|
11
|
+
* these passes on a laptop and fails in CI. Call this at the top of such a
|
|
12
|
+
* file or describe: every test in scope starts on a self-hosted install, a
|
|
13
|
+
* test about SaaS sets the variable itself, and the originals are restored
|
|
14
|
+
* afterwards.
|
|
15
|
+
*/
|
|
16
|
+
const EGRESS_POLICY_ENV_VARS: Array<string> = [
|
|
17
|
+
"BILLING_ENABLED",
|
|
18
|
+
"DATA_SOURCE_BLOCK_PRIVATE_ADDRESSES",
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
export function startEachTestOnSelfHostedEgressPolicy(): void {
|
|
22
|
+
const saved: Map<string, string | undefined> = new Map<
|
|
23
|
+
string,
|
|
24
|
+
string | undefined
|
|
25
|
+
>();
|
|
26
|
+
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
for (const name of EGRESS_POLICY_ENV_VARS) {
|
|
29
|
+
saved.set(name, process.env[name]);
|
|
30
|
+
delete process.env[name];
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
for (const name of EGRESS_POLICY_ENV_VARS) {
|
|
36
|
+
const value: string | undefined = saved.get(name);
|
|
37
|
+
|
|
38
|
+
if (value === undefined) {
|
|
39
|
+
delete process.env[name];
|
|
40
|
+
} else {
|
|
41
|
+
process.env[name] = value;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
import PinnedSmtpSocket, {
|
|
2
|
+
SmtpSocketCallback,
|
|
3
|
+
} from "../../../../Server/Utils/Mail/PinnedSmtpSocket";
|
|
4
|
+
import { ResolvedAddress } from "../../../../Server/Utils/DataSource/EgressGuard";
|
|
5
|
+
import { afterEach, describe, expect, test } from "@jest/globals";
|
|
6
|
+
import net from "net";
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* The socket half of pinning a tenant-chosen SMTP server.
|
|
10
|
+
*
|
|
11
|
+
* The check that an SMTP host is allowed happens before the connection, but
|
|
12
|
+
* nodemailer given a *host name* resolves it again itself, behind a five minute
|
|
13
|
+
* cache that ignores the record's TTL. So a name that answered with a public
|
|
14
|
+
* address during validation and a private one a moment later would be checked
|
|
15
|
+
* against the first and connected to the second. PinnedSmtpSocket closes that
|
|
16
|
+
* window: it dials the addresses the guard already validated and hands
|
|
17
|
+
* nodemailer a connected socket, leaving it nothing to resolve.
|
|
18
|
+
*
|
|
19
|
+
* Which makes the first thing worth proving the negative one — that the host
|
|
20
|
+
* name is *not* what gets dialed. These tests pass a name that cannot resolve
|
|
21
|
+
* at all together with a loopback address, and the connection still lands: if
|
|
22
|
+
* the pinned lookup were ever dropped, every one of them would fail instead.
|
|
23
|
+
*
|
|
24
|
+
* Real loopback sockets throughout rather than a mocked `net`; a mock of the
|
|
25
|
+
* module would assert the arguments this passes to net.connect and prove
|
|
26
|
+
* nothing about where the bytes went.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
// A name with a reserved TLD (RFC 2606): guaranteed never to resolve anywhere.
|
|
30
|
+
const UNRESOLVABLE_HOST: string = "smtp.pinning-must-not-resolve-this.invalid";
|
|
31
|
+
|
|
32
|
+
const CONNECT_TIMEOUT_MS: number = 5000;
|
|
33
|
+
|
|
34
|
+
/*
|
|
35
|
+
* Errors raised inside Node's `net` fail `toBeInstanceOf(Error)` here: the
|
|
36
|
+
* suite runs in a jsdom environment, so the `Error` the test sees and the one
|
|
37
|
+
* the socket threw come from different realms. Assert the shape that matters
|
|
38
|
+
* instead — a connection failure has to reach nodemailer as something with a
|
|
39
|
+
* message on it, whichever realm made it.
|
|
40
|
+
*/
|
|
41
|
+
function expectConnectionFailure(error: Error | null): Error {
|
|
42
|
+
expect(error).not.toBeNull();
|
|
43
|
+
expect(typeof error!.message).toBe("string");
|
|
44
|
+
expect(error!.message.length).toBeGreaterThan(0);
|
|
45
|
+
|
|
46
|
+
return error!;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface ConnectOutcome {
|
|
50
|
+
calls: number;
|
|
51
|
+
error: Error | null;
|
|
52
|
+
connection: net.Socket | undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let servers: Array<net.Server> = [];
|
|
56
|
+
let sockets: Array<net.Socket> = [];
|
|
57
|
+
|
|
58
|
+
// A listening loopback server, and the port it landed on.
|
|
59
|
+
function listen(): Promise<{ server: net.Server; port: number }> {
|
|
60
|
+
return new Promise(
|
|
61
|
+
(resolve: (value: { server: net.Server; port: number }) => void): void => {
|
|
62
|
+
const server: net.Server = net.createServer();
|
|
63
|
+
servers.push(server);
|
|
64
|
+
server.listen(0, "127.0.0.1", (): void => {
|
|
65
|
+
resolve({
|
|
66
|
+
server: server,
|
|
67
|
+
port: (server.address() as net.AddressInfo).port,
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// A port nothing is listening on: bound to learn the number, then released.
|
|
75
|
+
async function closedPort(): Promise<number> {
|
|
76
|
+
const { server, port } = await listen();
|
|
77
|
+
await new Promise((resolve: (value: unknown) => void): void => {
|
|
78
|
+
server.close(resolve);
|
|
79
|
+
});
|
|
80
|
+
return port;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/*
|
|
84
|
+
* Runs connect and resolves once onDone has fired, then waits a further tick
|
|
85
|
+
* so that a second call — which the `settled` guard exists to prevent — would
|
|
86
|
+
* be counted rather than missed.
|
|
87
|
+
*/
|
|
88
|
+
function connectAndWait(data: {
|
|
89
|
+
host: string;
|
|
90
|
+
port: number;
|
|
91
|
+
addresses: Array<ResolvedAddress>;
|
|
92
|
+
timeoutInMs?: number;
|
|
93
|
+
}): Promise<ConnectOutcome> {
|
|
94
|
+
return new Promise((resolve: (value: ConnectOutcome) => void): void => {
|
|
95
|
+
const outcome: ConnectOutcome = {
|
|
96
|
+
calls: 0,
|
|
97
|
+
error: null,
|
|
98
|
+
connection: undefined,
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const onDone: SmtpSocketCallback = (
|
|
102
|
+
error: Error | null,
|
|
103
|
+
socketOptions?: { connection: net.Socket },
|
|
104
|
+
): void => {
|
|
105
|
+
outcome.calls = outcome.calls + 1;
|
|
106
|
+
|
|
107
|
+
if (outcome.calls === 1) {
|
|
108
|
+
outcome.error = error;
|
|
109
|
+
outcome.connection = socketOptions?.connection;
|
|
110
|
+
|
|
111
|
+
if (socketOptions?.connection) {
|
|
112
|
+
sockets.push(socketOptions.connection);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
setTimeout((): void => {
|
|
116
|
+
resolve(outcome);
|
|
117
|
+
}, 50);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
PinnedSmtpSocket.connect({
|
|
122
|
+
host: data.host,
|
|
123
|
+
port: data.port,
|
|
124
|
+
addresses: data.addresses,
|
|
125
|
+
timeoutInMs: data.timeoutInMs ?? CONNECT_TIMEOUT_MS,
|
|
126
|
+
onDone: onDone,
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
afterEach(async () => {
|
|
132
|
+
for (const socket of sockets) {
|
|
133
|
+
socket.destroy();
|
|
134
|
+
}
|
|
135
|
+
sockets = [];
|
|
136
|
+
|
|
137
|
+
await Promise.all(
|
|
138
|
+
servers.map((server: net.Server): Promise<void> => {
|
|
139
|
+
return new Promise((resolve: () => void): void => {
|
|
140
|
+
server.close((): void => {
|
|
141
|
+
return resolve();
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
}),
|
|
145
|
+
);
|
|
146
|
+
servers = [];
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe("PinnedSmtpSocket.connect", () => {
|
|
150
|
+
test("connects to the validated address even though the host name does not resolve", async () => {
|
|
151
|
+
const { server, port } = await listen();
|
|
152
|
+
|
|
153
|
+
const accepted: Promise<net.Socket> = new Promise(
|
|
154
|
+
(resolve: (value: net.Socket) => void): void => {
|
|
155
|
+
server.once("connection", resolve);
|
|
156
|
+
},
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const outcome: ConnectOutcome = await connectAndWait({
|
|
160
|
+
host: UNRESOLVABLE_HOST,
|
|
161
|
+
port: port,
|
|
162
|
+
addresses: [{ address: "127.0.0.1", family: 4 }],
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
expect(outcome.error).toBeNull();
|
|
166
|
+
expect(outcome.connection).toBeInstanceOf(net.Socket);
|
|
167
|
+
|
|
168
|
+
/*
|
|
169
|
+
* The point of the whole class: the bytes went to the address the guard
|
|
170
|
+
* validated, not anywhere the name might have pointed.
|
|
171
|
+
*/
|
|
172
|
+
const serverSide: net.Socket = await accepted;
|
|
173
|
+
expect(serverSide.remoteAddress).toBe("127.0.0.1");
|
|
174
|
+
expect(outcome.connection!.remotePort).toBe(port);
|
|
175
|
+
serverSide.destroy();
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("hands back a socket that is already connected and usable", async () => {
|
|
179
|
+
const { server, port } = await listen();
|
|
180
|
+
|
|
181
|
+
const received: Promise<string> = new Promise(
|
|
182
|
+
(resolve: (value: string) => void): void => {
|
|
183
|
+
server.once("connection", (socket: net.Socket): void => {
|
|
184
|
+
socket.once("data", (chunk: Buffer): void => {
|
|
185
|
+
resolve(chunk.toString());
|
|
186
|
+
socket.destroy();
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
},
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
const outcome: ConnectOutcome = await connectAndWait({
|
|
193
|
+
host: UNRESOLVABLE_HOST,
|
|
194
|
+
port: port,
|
|
195
|
+
addresses: [{ address: "127.0.0.1", family: 4 }],
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
expect(outcome.connection!.connecting).toBe(false);
|
|
199
|
+
expect(outcome.connection!.destroyed).toBe(false);
|
|
200
|
+
|
|
201
|
+
// nodemailer writes its EHLO onto exactly this socket.
|
|
202
|
+
outcome.connection!.write("EHLO pinned\r\n");
|
|
203
|
+
await expect(received).resolves.toBe("EHLO pinned\r\n");
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("reports the address it was given, not the name, so onDone fires once", async () => {
|
|
207
|
+
const port: number = await closedPort();
|
|
208
|
+
|
|
209
|
+
const outcome: ConnectOutcome = await connectAndWait({
|
|
210
|
+
host: UNRESOLVABLE_HOST,
|
|
211
|
+
port: port,
|
|
212
|
+
addresses: [{ address: "127.0.0.1", family: 4 }],
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
/*
|
|
216
|
+
* A refused connection, not a DNS failure: the name never went to a
|
|
217
|
+
* resolver. ECONNREFUSED names the address that refused.
|
|
218
|
+
*/
|
|
219
|
+
expect(expectConnectionFailure(outcome.error).message).toContain(
|
|
220
|
+
"127.0.0.1",
|
|
221
|
+
);
|
|
222
|
+
expect(outcome.connection).toBeUndefined();
|
|
223
|
+
expect(outcome.calls).toBe(1);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test("a failed connection does not leave a live socket behind", async () => {
|
|
227
|
+
const port: number = await closedPort();
|
|
228
|
+
|
|
229
|
+
const outcome: ConnectOutcome = await connectAndWait({
|
|
230
|
+
host: UNRESOLVABLE_HOST,
|
|
231
|
+
port: port,
|
|
232
|
+
addresses: [{ address: "127.0.0.1", family: 4 }],
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
expect(outcome.error).not.toBeNull();
|
|
236
|
+
expect(outcome.connection).toBeUndefined();
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test("a socket that never answers ends the attempt instead of hanging the send", async () => {
|
|
240
|
+
/*
|
|
241
|
+
* 192.0.2.0/24 is TEST-NET-1 (RFC 5737) and is not routed, so the SYN goes
|
|
242
|
+
* unanswered and setTimeout is what ends the attempt. Only the invariants
|
|
243
|
+
* are asserted, not the message: a network that answers with an ICMP
|
|
244
|
+
* unreachable instead of dropping the packet takes the error path, and
|
|
245
|
+
* both outcomes are the behaviour under test — the send ends, once,
|
|
246
|
+
* rather than waiting on nodemailer's own timeout, which has not started.
|
|
247
|
+
*/
|
|
248
|
+
const outcome: ConnectOutcome = await connectAndWait({
|
|
249
|
+
host: UNRESOLVABLE_HOST,
|
|
250
|
+
port: 25,
|
|
251
|
+
addresses: [{ address: "192.0.2.1", family: 4 }],
|
|
252
|
+
timeoutInMs: 150,
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
expectConnectionFailure(outcome.error);
|
|
256
|
+
expect(outcome.connection).toBeUndefined();
|
|
257
|
+
expect(outcome.calls).toBe(1);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test("onDone is called once even when the connected socket errors afterwards", async () => {
|
|
261
|
+
// afterEach closes the server; only the port it landed on is needed here.
|
|
262
|
+
const { port } = await listen();
|
|
263
|
+
|
|
264
|
+
const outcome: ConnectOutcome = await new Promise(
|
|
265
|
+
(resolve: (value: ConnectOutcome) => void): void => {
|
|
266
|
+
const seen: ConnectOutcome = {
|
|
267
|
+
calls: 0,
|
|
268
|
+
error: null,
|
|
269
|
+
connection: undefined,
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
PinnedSmtpSocket.connect({
|
|
273
|
+
host: UNRESOLVABLE_HOST,
|
|
274
|
+
port: port,
|
|
275
|
+
addresses: [{ address: "127.0.0.1", family: 4 }],
|
|
276
|
+
timeoutInMs: CONNECT_TIMEOUT_MS,
|
|
277
|
+
onDone: (
|
|
278
|
+
error: Error | null,
|
|
279
|
+
socketOptions?: { connection: net.Socket },
|
|
280
|
+
): void => {
|
|
281
|
+
seen.calls = seen.calls + 1;
|
|
282
|
+
seen.error = error;
|
|
283
|
+
seen.connection = socketOptions?.connection;
|
|
284
|
+
|
|
285
|
+
if (seen.calls > 1 || !socketOptions?.connection) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const socket: net.Socket = socketOptions.connection;
|
|
290
|
+
sockets.push(socket);
|
|
291
|
+
|
|
292
|
+
/*
|
|
293
|
+
* What nodemailer does on being handed the socket, and the reason
|
|
294
|
+
* the class installs its own no-op listener: without an error
|
|
295
|
+
* handler an ECONNRESET here is an unhandled 'error' event, which
|
|
296
|
+
* takes the process down.
|
|
297
|
+
*/
|
|
298
|
+
socket.on("error", (): void => {});
|
|
299
|
+
socket.destroy(new Error("reset by peer"));
|
|
300
|
+
|
|
301
|
+
setTimeout((): void => {
|
|
302
|
+
resolve(seen);
|
|
303
|
+
}, 50);
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
},
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
expect(outcome.calls).toBe(1);
|
|
310
|
+
expect(outcome.error).toBeNull();
|
|
311
|
+
expect(outcome.connection!.destroyed).toBe(true);
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
describe("PinnedSmtpSocket.describeConnectionError", () => {
|
|
316
|
+
test("an error that already says something is passed straight through", () => {
|
|
317
|
+
const error: Error = new Error("connect ECONNREFUSED 10.0.0.5:587");
|
|
318
|
+
|
|
319
|
+
expect(PinnedSmtpSocket.describeConnectionError(error)).toBe(error);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
test("an AggregateError with an empty message is replaced by what each attempt said", () => {
|
|
323
|
+
/*
|
|
324
|
+
* What autoSelectFamily reports when every pinned address fails: an
|
|
325
|
+
* AggregateError whose own message is empty. Left alone it reaches the
|
|
326
|
+
* user as a bare "could not be sent" with nothing to act on.
|
|
327
|
+
*/
|
|
328
|
+
const aggregate: Error & { errors?: Array<Error> } = new Error("");
|
|
329
|
+
aggregate.errors = [
|
|
330
|
+
new Error("connect ECONNREFUSED 10.0.0.5:587"),
|
|
331
|
+
new Error("connect ETIMEDOUT 10.0.0.6:587"),
|
|
332
|
+
];
|
|
333
|
+
|
|
334
|
+
const described: Error =
|
|
335
|
+
PinnedSmtpSocket.describeConnectionError(aggregate);
|
|
336
|
+
|
|
337
|
+
expect(described).not.toBe(aggregate);
|
|
338
|
+
expect(described.message).toBe(
|
|
339
|
+
"connect ECONNREFUSED 10.0.0.5:587; connect ETIMEDOUT 10.0.0.6:587",
|
|
340
|
+
);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
test("the code is carried over so callers that branch on it still can", () => {
|
|
344
|
+
const aggregate: Error & { errors?: Array<Error>; code?: string } =
|
|
345
|
+
new Error("");
|
|
346
|
+
aggregate.errors = [new Error("connect ECONNREFUSED 10.0.0.5:587")];
|
|
347
|
+
aggregate.code = "ECONNREFUSED";
|
|
348
|
+
|
|
349
|
+
const described: NodeJS.ErrnoException =
|
|
350
|
+
PinnedSmtpSocket.describeConnectionError(aggregate);
|
|
351
|
+
|
|
352
|
+
expect(described.code).toBe("ECONNREFUSED");
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
test("no code on the aggregate leaves none on the description", () => {
|
|
356
|
+
const aggregate: Error & { errors?: Array<Error> } = new Error("");
|
|
357
|
+
aggregate.errors = [new Error("connect ECONNREFUSED 10.0.0.5:587")];
|
|
358
|
+
|
|
359
|
+
const described: NodeJS.ErrnoException =
|
|
360
|
+
PinnedSmtpSocket.describeConnectionError(aggregate);
|
|
361
|
+
|
|
362
|
+
expect(described.code).toBeUndefined();
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
test("a message wins over the attempts, so a described error is never rewritten twice", () => {
|
|
366
|
+
const aggregate: Error & { errors?: Array<Error> } = new Error(
|
|
367
|
+
"everything failed",
|
|
368
|
+
);
|
|
369
|
+
aggregate.errors = [new Error("connect ECONNREFUSED 10.0.0.5:587")];
|
|
370
|
+
|
|
371
|
+
expect(PinnedSmtpSocket.describeConnectionError(aggregate)).toBe(aggregate);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
test("an empty message with no attempts to describe is passed through", () => {
|
|
375
|
+
const error: Error = new Error("");
|
|
376
|
+
|
|
377
|
+
expect(PinnedSmtpSocket.describeConnectionError(error)).toBe(error);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
test("an empty attempt list is passed through rather than turned into an empty message", () => {
|
|
381
|
+
const aggregate: Error & { errors?: Array<Error> } = new Error("");
|
|
382
|
+
aggregate.errors = [];
|
|
383
|
+
|
|
384
|
+
expect(PinnedSmtpSocket.describeConnectionError(aggregate)).toBe(aggregate);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
test("an errors property that is not a list is left alone", () => {
|
|
388
|
+
const odd: Error & { errors?: unknown } = new Error("");
|
|
389
|
+
odd.errors = { first: "connect ECONNREFUSED" };
|
|
390
|
+
|
|
391
|
+
expect(PinnedSmtpSocket.describeConnectionError(odd)).toBe(odd);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
test("attempts that are not errors are still described rather than dropped", () => {
|
|
395
|
+
const aggregate: Error & { errors?: Array<unknown> } = new Error("");
|
|
396
|
+
aggregate.errors = [
|
|
397
|
+
"plain string failure",
|
|
398
|
+
null,
|
|
399
|
+
{ message: "an object with a message" },
|
|
400
|
+
];
|
|
401
|
+
|
|
402
|
+
const described: Error =
|
|
403
|
+
PinnedSmtpSocket.describeConnectionError(aggregate);
|
|
404
|
+
|
|
405
|
+
expect(described.message).toBe(
|
|
406
|
+
"plain string failure; null; an object with a message",
|
|
407
|
+
);
|
|
408
|
+
});
|
|
409
|
+
});
|
|
@@ -896,6 +896,29 @@ describe("MonitorCriteriaObservationBuilder.describeFilterObservation — networ
|
|
|
896
896
|
).toBe("Device is unreachable by ping and SNMP: Request timed out");
|
|
897
897
|
});
|
|
898
898
|
|
|
899
|
+
/*
|
|
900
|
+
* A probe with no IPv6 never contacted the device. Its cause says so, and
|
|
901
|
+
* an "unreachable" lead-in in front of it would contradict it.
|
|
902
|
+
*/
|
|
903
|
+
test("Is Online does not call a device unreachable when the probe could not check it", () => {
|
|
904
|
+
const probeSideCause: string =
|
|
905
|
+
"This probe cannot send IPv6 traffic (send EADDRNOTAVAIL 2001:518:2800:9::2:161), so 2001:518:2800:9::2 was never contacted. The probe has no usable IPv6 address or route; this says nothing about whether 2001:518:2800:9::2 is up. Monitor IPv6 destinations from a probe that has IPv6 connectivity.";
|
|
906
|
+
|
|
907
|
+
const observation: string | null =
|
|
908
|
+
MonitorCriteriaObservationBuilder.describeFilterObservation(
|
|
909
|
+
buildProbeInputs({
|
|
910
|
+
checkOn: CheckOn.SnmpIsOnline,
|
|
911
|
+
isOnline: false,
|
|
912
|
+
failureCause: probeSideCause,
|
|
913
|
+
}),
|
|
914
|
+
);
|
|
915
|
+
|
|
916
|
+
expect(observation).toBe(
|
|
917
|
+
`Device could not be checked from this probe: ${probeSideCause}`,
|
|
918
|
+
);
|
|
919
|
+
expect(observation).not.toContain("unreachable");
|
|
920
|
+
});
|
|
921
|
+
|
|
899
922
|
test("Walk Is Succeeding names the no-walk state rather than calling it a failure", () => {
|
|
900
923
|
expect(
|
|
901
924
|
MonitorCriteriaObservationBuilder.describeFilterObservation(
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
test,
|
|
10
10
|
} from "@jest/globals";
|
|
11
11
|
import dns from "dns";
|
|
12
|
+
import { startEachTestOnSelfHostedEgressPolicy } from "./EgressPolicyEnvironment";
|
|
12
13
|
|
|
13
14
|
/*
|
|
14
15
|
* SSRFProtection is the single guard standing between attacker-controlled
|
|
@@ -30,6 +31,9 @@ type LookupSpy = jest.SpiedFunction<
|
|
|
30
31
|
) => Promise<Array<{ address: string; family: number }>>
|
|
31
32
|
>;
|
|
32
33
|
|
|
34
|
+
// Detailed refusals are asserted throughout; SaaS behaviour is tested apart.
|
|
35
|
+
startEachTestOnSelfHostedEgressPolicy();
|
|
36
|
+
|
|
33
37
|
describe("SSRFProtection.validateWebhookTargetIsSafe", () => {
|
|
34
38
|
let lookupSpy: LookupSpy;
|
|
35
39
|
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
test,
|
|
9
9
|
} from "@jest/globals";
|
|
10
10
|
import dns from "dns";
|
|
11
|
+
import { startEachTestOnSelfHostedEgressPolicy } from "./EgressPolicyEnvironment";
|
|
11
12
|
|
|
12
13
|
const ALLOW_PRIVATE_NETWORKS_ENV: string = "ALLOW_PRIVATE_NETWORK_WEBHOOKS";
|
|
13
14
|
const PRIVATE_NETWORK_ALLOWLIST_ENV: string =
|
|
@@ -252,6 +253,9 @@ const nonGlobalPrivateAddresses: Array<AddressCase> = [
|
|
|
252
253
|
},
|
|
253
254
|
];
|
|
254
255
|
|
|
256
|
+
// Detailed refusals are asserted throughout; SaaS behaviour is tested apart.
|
|
257
|
+
startEachTestOnSelfHostedEgressPolicy();
|
|
258
|
+
|
|
255
259
|
describe("SSRFProtection — cloud service and translation addresses", () => {
|
|
256
260
|
let lookupSpy: LookupSpy;
|
|
257
261
|
let originalAllowPrivateNetworks: string | undefined;
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
test,
|
|
9
9
|
} from "@jest/globals";
|
|
10
10
|
import dns from "dns";
|
|
11
|
+
import { startEachTestOnSelfHostedEgressPolicy } from "./EgressPolicyEnvironment";
|
|
11
12
|
|
|
12
13
|
/*
|
|
13
14
|
* The private-network webhook exception (issue #3424).
|
|
@@ -44,6 +45,9 @@ const OPTED_IN: { allowPrivateNetworkTargets: boolean } = {
|
|
|
44
45
|
allowPrivateNetworkTargets: true,
|
|
45
46
|
};
|
|
46
47
|
|
|
48
|
+
// Detailed refusals are asserted throughout; SaaS behaviour is tested apart.
|
|
49
|
+
startEachTestOnSelfHostedEgressPolicy();
|
|
50
|
+
|
|
47
51
|
describe("SSRFProtection — private network opt-in", () => {
|
|
48
52
|
let lookupSpy: LookupSpy;
|
|
49
53
|
let originalAllow: string | undefined;
|