@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
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import LIMIT_MAX from "../../../Types/Database/LimitMax";
|
|
2
|
+
import BadDataException from "../../../Types/Exception/BadDataException";
|
|
3
|
+
import NotAuthorizedException from "../../../Types/Exception/NotAuthorizedException";
|
|
4
|
+
import StatusPageSubscriberNotificationStatus from "../../../Types/StatusPage/StatusPageSubscriberNotificationStatus";
|
|
5
|
+
import SubscriberNotificationResend from "../../../Types/StatusPage/SubscriberNotificationResend";
|
|
6
|
+
import DatabaseRequestType from "../../Types/BaseDatabase/DatabaseRequestType";
|
|
7
|
+
import ColumnPermissions from "../../Types/Database/Permissions/ColumnPermission";
|
|
8
|
+
import ModelPermission from "../../Types/Database/Permissions/Index";
|
|
9
|
+
import TablePermission from "../../Types/Database/Permissions/TablePermission";
|
|
10
|
+
/*
|
|
11
|
+
* Who may send a status page subscriber notification again, and when.
|
|
12
|
+
*
|
|
13
|
+
* Sending one again is a write: its status goes back to Pending, and the
|
|
14
|
+
* job that sends it picks it up. So the update's own permission check - the
|
|
15
|
+
* table's update access control and each written column's - already decides
|
|
16
|
+
* who may. For a public note that is not enough: the note's edit permission
|
|
17
|
+
* is granted on its own, and editing a note does not make someone its
|
|
18
|
+
* poster. Sending a note's notification again tells every subscriber of the
|
|
19
|
+
* incident's status pages what the note says, which is what posting it did,
|
|
20
|
+
* so it also needs the permission to post a note that notifies subscribers.
|
|
21
|
+
* The same goes for telling subscribers about an edit (the note's 'updated'
|
|
22
|
+
* notification): an editor can change the text first, so with the edit
|
|
23
|
+
* permission alone it would be a way to message every subscriber.
|
|
24
|
+
*
|
|
25
|
+
* When: never while the notification is being sent. The send settles its
|
|
26
|
+
* own status when it finishes, so a Pending written over a running send
|
|
27
|
+
* either lets a second run send it at the same time - the same subscribers
|
|
28
|
+
* messaged twice, each run then overwriting the other's status - or is
|
|
29
|
+
* overwritten and lost without a word. That holds for every notification a
|
|
30
|
+
* user can send again (assertNotQueuedWhileBeingSent).
|
|
31
|
+
*
|
|
32
|
+
* These checks run in the services' onBeforeUpdate, before the update's own
|
|
33
|
+
* check (DatabaseService runs that after the hooks), so each one checks
|
|
34
|
+
* permissions before it reads anything: a caller who may not send a
|
|
35
|
+
* notification again is told only that, never what state the notification
|
|
36
|
+
* is in. Rows are then read with the caller's own permissions, so nothing
|
|
37
|
+
* they cannot read decides the answer either.
|
|
38
|
+
*
|
|
39
|
+
* Root callers - the workers and other internal writes - are trusted and
|
|
40
|
+
* never checked. Master admins skip the permission checks, as they do
|
|
41
|
+
* everywhere, but not the checks on the notification's state.
|
|
42
|
+
*/
|
|
43
|
+
class SubscriberNotificationResendAccess {
|
|
44
|
+
static isPermissionChecked(props) {
|
|
45
|
+
return !props.isRoot && !props.isMasterAdmin;
|
|
46
|
+
}
|
|
47
|
+
/*
|
|
48
|
+
* Throws `refusal` unless the caller may write these columns of the model:
|
|
49
|
+
* the table's update access control, then each column's. A request that
|
|
50
|
+
* fails for any other reason - an expired session, an unpaid plan - keeps
|
|
51
|
+
* its own error.
|
|
52
|
+
*/
|
|
53
|
+
static assertCallerMayUpdateColumns(data) {
|
|
54
|
+
if (!this.isPermissionChecked(data.props)) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
this.withRefusal(data.refusal, () => {
|
|
58
|
+
TablePermission.checkTableLevelPermissions(data.modelType, data.props, DatabaseRequestType.Update);
|
|
59
|
+
ColumnPermissions.checkDataColumnPermissions(data.modelType, data.columns, data.props, DatabaseRequestType.Update);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/*
|
|
63
|
+
* Throws `refusal` unless the caller may post a new note of this type
|
|
64
|
+
* that notifies subscribers: the create check a post with Notify Status
|
|
65
|
+
* Page Subscribers ticked goes through, on a note carrying only that
|
|
66
|
+
* choice.
|
|
67
|
+
*/
|
|
68
|
+
static assertCallerMayPostNotifyingNote(data) {
|
|
69
|
+
if (!this.isPermissionChecked(data.props)) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const probe = new data.modelType();
|
|
73
|
+
probe["shouldStatusPageSubscribersBeNotifiedOnNoteCreated"] = true;
|
|
74
|
+
this.withRefusal(data.refusal, () => {
|
|
75
|
+
ModelPermission.checkCreatePermissions(data.modelType, probe, data.props);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/*
|
|
79
|
+
* A user's or an API key's request to send a public note's 'posted'
|
|
80
|
+
* notification again - an update writing Pending into
|
|
81
|
+
* subscriberNotificationStatusOnNoteCreated, which is what the dashboard's
|
|
82
|
+
* Retry and Resend do. Shared by the incident, incident episode and
|
|
83
|
+
* scheduled maintenance public notes.
|
|
84
|
+
*
|
|
85
|
+
* - The caller must be able to edit the note's notification status and to
|
|
86
|
+
* post a note that notifies subscribers (see the header).
|
|
87
|
+
* - Every note the update matches must be one whose notification can be
|
|
88
|
+
* sent again (SubscriberNotificationResend.getPublicNoteResendRefusal):
|
|
89
|
+
* not a note posted without notifying subscribers, which the job never
|
|
90
|
+
* sends, and not one being sent right now, whose send would overwrite
|
|
91
|
+
* the request when it finishes.
|
|
92
|
+
*
|
|
93
|
+
* An update that does not ask for it is not looked at.
|
|
94
|
+
*/
|
|
95
|
+
static async assertPublicNoteResendAllowed(data) {
|
|
96
|
+
const updateBy = data.updateBy;
|
|
97
|
+
if (updateBy.props.isRoot ||
|
|
98
|
+
!SubscriberNotificationResend.isPublicNoteResendRequested(updateBy.data)) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
this.assertCallerMayUpdateColumns({
|
|
102
|
+
modelType: data.modelType,
|
|
103
|
+
columns: this.PUBLIC_NOTE_RESEND_COLUMNS,
|
|
104
|
+
props: updateBy.props,
|
|
105
|
+
refusal: SubscriberNotificationResend.noPermissionToResendNoteMessage,
|
|
106
|
+
});
|
|
107
|
+
this.assertCallerMayPostNotifyingNote({
|
|
108
|
+
modelType: data.modelType,
|
|
109
|
+
props: updateBy.props,
|
|
110
|
+
refusal: SubscriberNotificationResend.noPermissionToResendNoteMessage,
|
|
111
|
+
});
|
|
112
|
+
const notes = await data.service.findBy({
|
|
113
|
+
query: updateBy.query,
|
|
114
|
+
select: {
|
|
115
|
+
_id: true,
|
|
116
|
+
subscriberNotificationStatusOnNoteCreated: true,
|
|
117
|
+
shouldStatusPageSubscribersBeNotifiedOnNoteCreated: true,
|
|
118
|
+
},
|
|
119
|
+
limit: LIMIT_MAX,
|
|
120
|
+
skip: 0,
|
|
121
|
+
props: updateBy.props,
|
|
122
|
+
});
|
|
123
|
+
for (const note of notes) {
|
|
124
|
+
const record = note;
|
|
125
|
+
const refusal = SubscriberNotificationResend.getPublicNoteResendRefusal({
|
|
126
|
+
status: record["subscriberNotificationStatusOnNoteCreated"],
|
|
127
|
+
shouldStatusPageSubscribersBeNotifiedOnNoteCreated: record["shouldStatusPageSubscribersBeNotifiedOnNoteCreated"],
|
|
128
|
+
});
|
|
129
|
+
if (refusal) {
|
|
130
|
+
throw new BadDataException(refusal);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/*
|
|
135
|
+
* A user's or an API key's request to tell subscribers about an edit to a
|
|
136
|
+
* public note: the edit carries the notify-on-edit misc data prop
|
|
137
|
+
* (SubscriberUpdateNotification, which the service has turned into a
|
|
138
|
+
* Pending by now), or writes Pending into
|
|
139
|
+
* subscriberNotificationStatusOnNoteUpdated itself, as the dashboard's
|
|
140
|
+
* Retry of a failed update does. Shared by the incident, incident episode
|
|
141
|
+
* and scheduled maintenance public notes.
|
|
142
|
+
*
|
|
143
|
+
* - The caller must be able to write the update notification's status and
|
|
144
|
+
* to post a note that notifies subscribers (see the header).
|
|
145
|
+
* - It is refused while the update notification is being sent
|
|
146
|
+
* (assertNotQueuedWhileBeingSent): the edit can be saved without it.
|
|
147
|
+
*
|
|
148
|
+
* An update that does not ask for it is not looked at.
|
|
149
|
+
*/
|
|
150
|
+
static async assertPublicNoteUpdateNotificationAllowed(data) {
|
|
151
|
+
const updateBy = data.updateBy;
|
|
152
|
+
if (updateBy.props.isRoot ||
|
|
153
|
+
updateBy.data["subscriberNotificationStatusOnNoteUpdated"] !== StatusPageSubscriberNotificationStatus.Pending) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
this.assertCallerMayUpdateColumns({
|
|
157
|
+
modelType: data.modelType,
|
|
158
|
+
columns: this.PUBLIC_NOTE_UPDATE_NOTIFICATION_COLUMNS,
|
|
159
|
+
props: updateBy.props,
|
|
160
|
+
refusal: SubscriberNotificationResend.noPermissionToNotifyAboutEditMessage,
|
|
161
|
+
});
|
|
162
|
+
this.assertCallerMayPostNotifyingNote({
|
|
163
|
+
modelType: data.modelType,
|
|
164
|
+
props: updateBy.props,
|
|
165
|
+
refusal: SubscriberNotificationResend.noPermissionToNotifyAboutEditMessage,
|
|
166
|
+
});
|
|
167
|
+
await this.assertNotQueuedWhileBeingSent({
|
|
168
|
+
modelType: data.modelType,
|
|
169
|
+
service: data.service,
|
|
170
|
+
updateBy: updateBy,
|
|
171
|
+
statusColumns: ["subscriberNotificationStatusOnNoteUpdated"],
|
|
172
|
+
refusal: SubscriberNotificationResend.updateBeingSentMessage,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
/*
|
|
176
|
+
* A user's or an API key's update writing Pending into a notification's
|
|
177
|
+
* status column - Retry, Resend, or the API route of sending it again -
|
|
178
|
+
* while that notification is being sent (InProgress). Refused with
|
|
179
|
+
* `refusal` (SubscriberNotificationResend.beingSentMessage by default):
|
|
180
|
+
* see the header for why.
|
|
181
|
+
*
|
|
182
|
+
* Only the columns the update writes Pending into are looked at, so an
|
|
183
|
+
* edit that leaves the notification alone is never refused, and Pending
|
|
184
|
+
* written over Pending (queued already) changes nothing and is let
|
|
185
|
+
* through.
|
|
186
|
+
*
|
|
187
|
+
* Permission first: a caller who may not write those columns is left to
|
|
188
|
+
* the update's own check, which refuses them anyway, and never learns the
|
|
189
|
+
* notification's state from this one. The rows are then read with the
|
|
190
|
+
* caller's own permissions, so nothing they cannot read decides the
|
|
191
|
+
* answer. Root callers - the workers - are never checked.
|
|
192
|
+
*/
|
|
193
|
+
static async assertNotQueuedWhileBeingSent(data) {
|
|
194
|
+
const updateBy = data.updateBy;
|
|
195
|
+
if (updateBy.props.isRoot) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const written = (updateBy.data || {});
|
|
199
|
+
const queuedColumns = data.statusColumns.filter((column) => {
|
|
200
|
+
return (written[column] === StatusPageSubscriberNotificationStatus.Pending);
|
|
201
|
+
});
|
|
202
|
+
if (queuedColumns.length === 0) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const pendingColumns = {};
|
|
206
|
+
const select = { _id: true };
|
|
207
|
+
for (const column of queuedColumns) {
|
|
208
|
+
pendingColumns[column] = StatusPageSubscriberNotificationStatus.Pending;
|
|
209
|
+
select[column] = true;
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
this.assertCallerMayUpdateColumns({
|
|
213
|
+
modelType: data.modelType,
|
|
214
|
+
columns: pendingColumns,
|
|
215
|
+
props: updateBy.props,
|
|
216
|
+
refusal: "",
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
if (err instanceof NotAuthorizedException) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
throw err;
|
|
224
|
+
}
|
|
225
|
+
const rows = await data.service.findBy({
|
|
226
|
+
query: updateBy.query,
|
|
227
|
+
select: select,
|
|
228
|
+
limit: LIMIT_MAX,
|
|
229
|
+
skip: 0,
|
|
230
|
+
props: updateBy.props,
|
|
231
|
+
});
|
|
232
|
+
for (const row of rows) {
|
|
233
|
+
const record = row;
|
|
234
|
+
for (const column of queuedColumns) {
|
|
235
|
+
if (record[column] === StatusPageSubscriberNotificationStatus.InProgress) {
|
|
236
|
+
throw new BadDataException(data.refusal || SubscriberNotificationResend.beingSentMessage);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/*
|
|
242
|
+
* Runs a permission check and turns its refusal into `refusal`, which says
|
|
243
|
+
* what the caller was trying to do. A column refusal is a BadDataException
|
|
244
|
+
* ("User is not allowed to update on ... column"), a table refusal a
|
|
245
|
+
* NotAuthorizedException; both become a NotAuthorizedException. Anything
|
|
246
|
+
* else - an anonymous caller, an unpaid plan - passes through as it is.
|
|
247
|
+
*/
|
|
248
|
+
static withRefusal(refusal, check) {
|
|
249
|
+
try {
|
|
250
|
+
check();
|
|
251
|
+
}
|
|
252
|
+
catch (err) {
|
|
253
|
+
if (err instanceof NotAuthorizedException ||
|
|
254
|
+
err instanceof BadDataException) {
|
|
255
|
+
throw new NotAuthorizedException(refusal);
|
|
256
|
+
}
|
|
257
|
+
throw err;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/*
|
|
262
|
+
* The columns of a public note that sending its 'posted' notification
|
|
263
|
+
* again writes.
|
|
264
|
+
*/
|
|
265
|
+
SubscriberNotificationResendAccess.PUBLIC_NOTE_RESEND_COLUMNS = {
|
|
266
|
+
subscriberNotificationStatusOnNoteCreated: StatusPageSubscriberNotificationStatus.Pending,
|
|
267
|
+
subscriberNotificationStatusMessage: "",
|
|
268
|
+
};
|
|
269
|
+
/*
|
|
270
|
+
* The columns of a public note that telling subscribers about an edit
|
|
271
|
+
* writes.
|
|
272
|
+
*/
|
|
273
|
+
SubscriberNotificationResendAccess.PUBLIC_NOTE_UPDATE_NOTIFICATION_COLUMNS = {
|
|
274
|
+
subscriberNotificationStatusOnNoteUpdated: StatusPageSubscriberNotificationStatus.Pending,
|
|
275
|
+
subscriberNotificationStatusMessageOnNoteUpdated: "",
|
|
276
|
+
};
|
|
277
|
+
export default SubscriberNotificationResendAccess;
|
|
278
|
+
//# sourceMappingURL=SubscriberNotificationResendAccess.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SubscriberNotificationResendAccess.js","sourceRoot":"","sources":["../../../../../Server/Utils/StatusPage/SubscriberNotificationResendAccess.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import OneUptimeDate from "../../../Types/Date";
|
|
2
|
+
import Semaphore, { SemaphoreLockTimeoutError, } from "../../Infrastructure/Semaphore";
|
|
3
|
+
import logger from "../Logger";
|
|
4
|
+
const parsePositiveIntFromEnv = (envKey, fallback) => {
|
|
5
|
+
const rawValue = process.env[envKey];
|
|
6
|
+
if (!rawValue) {
|
|
7
|
+
return fallback;
|
|
8
|
+
}
|
|
9
|
+
const parsedValue = parseInt(rawValue, 10);
|
|
10
|
+
if (!Number.isFinite(parsedValue) || parsedValue <= 0) {
|
|
11
|
+
return fallback;
|
|
12
|
+
}
|
|
13
|
+
return parsedValue;
|
|
14
|
+
};
|
|
15
|
+
// Held when there is nothing to bound: no project, or no Redis.
|
|
16
|
+
const NO_SLOT = {
|
|
17
|
+
release: async () => { },
|
|
18
|
+
};
|
|
19
|
+
class SubscriberNotificationRunLimit {
|
|
20
|
+
/*
|
|
21
|
+
* The job's run, run only while it holds one of the job's permits. For
|
|
22
|
+
* RunCron: RunCron("Job:Name", options, limit("Job:Name", async () => {})).
|
|
23
|
+
*/
|
|
24
|
+
static limit(jobName, run) {
|
|
25
|
+
return async () => {
|
|
26
|
+
await SubscriberNotificationRunLimit.runWithinLimit(jobName, run);
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/*
|
|
30
|
+
* One of the project's slots for this job, taken before a run claims one
|
|
31
|
+
* of the project's notifications; release it once that notification has
|
|
32
|
+
* settled. Null when the project already has
|
|
33
|
+
* MAX_CONCURRENT_SENDS_PER_PROJECT of this job's notifications being sent:
|
|
34
|
+
* leave this one Pending for a later run, and go on to the next. Never
|
|
35
|
+
* throws: without a project, or without Redis, the send goes ahead
|
|
36
|
+
* unbounded, as a run does without its permit.
|
|
37
|
+
*/
|
|
38
|
+
static async takeProjectSlot(data) {
|
|
39
|
+
var _a;
|
|
40
|
+
const projectId = ((_a = data.projectId) === null || _a === void 0 ? void 0 : _a.toString().trim()) || "";
|
|
41
|
+
if (!projectId) {
|
|
42
|
+
return NO_SLOT;
|
|
43
|
+
}
|
|
44
|
+
let permit;
|
|
45
|
+
try {
|
|
46
|
+
permit = await Semaphore.acquirePermit({
|
|
47
|
+
key: `${data.jobName}:${projectId.toLowerCase()}`,
|
|
48
|
+
namespace: SubscriberNotificationRunLimit.PROJECT_NAMESPACE,
|
|
49
|
+
limit: SubscriberNotificationRunLimit.MAX_CONCURRENT_SENDS_PER_PROJECT,
|
|
50
|
+
lockTimeout: SubscriberNotificationRunLimit.PERMIT_TTL_IN_MS,
|
|
51
|
+
acquireAttemptsLimit: 1,
|
|
52
|
+
acquireTimeout: 5000,
|
|
53
|
+
retryInterval: 50,
|
|
54
|
+
onLockLost: (err) => {
|
|
55
|
+
logger.warn(`${data.jobName}: lost the slot of project ${projectId} while sending: ${err.message}`);
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
if (err instanceof SemaphoreLockTimeoutError) {
|
|
61
|
+
logger.debug(`${data.jobName}: project ${projectId} already has ${SubscriberNotificationRunLimit.MAX_CONCURRENT_SENDS_PER_PROJECT} notification(s) being sent; leaving this one Pending.`);
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
logger.warn(`${data.jobName}: could not take a slot for project ${projectId} (${err instanceof Error ? err.message : String(err)}); sending without one.`);
|
|
65
|
+
return NO_SLOT;
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
release: async () => {
|
|
69
|
+
try {
|
|
70
|
+
await Semaphore.releasePermit(permit);
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
// It lapses on its own after PERMIT_TTL_IN_MS.
|
|
74
|
+
logger.warn(`${data.jobName}: could not release the slot of project ${projectId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
static async runWithinLimit(jobName, run) {
|
|
80
|
+
let permit = null;
|
|
81
|
+
try {
|
|
82
|
+
permit = await Semaphore.acquirePermit({
|
|
83
|
+
key: jobName,
|
|
84
|
+
namespace: SubscriberNotificationRunLimit.NAMESPACE,
|
|
85
|
+
limit: SubscriberNotificationRunLimit.MAX_CONCURRENT_RUNS_PER_JOB,
|
|
86
|
+
lockTimeout: SubscriberNotificationRunLimit.PERMIT_TTL_IN_MS,
|
|
87
|
+
// One look: a full set of permits means other runs are sending.
|
|
88
|
+
acquireAttemptsLimit: 1,
|
|
89
|
+
acquireTimeout: 5000,
|
|
90
|
+
retryInterval: 50,
|
|
91
|
+
onLockLost: (err) => {
|
|
92
|
+
/*
|
|
93
|
+
* Redis lost the permit (it could not be refreshed). The run goes
|
|
94
|
+
* on - its claims keep its notifications its own - and another
|
|
95
|
+
* run may take the freed slot meanwhile.
|
|
96
|
+
*/
|
|
97
|
+
logger.warn(`${jobName}: lost its run permit while sending: ${err.message}`);
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
if (err instanceof SemaphoreLockTimeoutError) {
|
|
103
|
+
logger.debug(`${jobName}: ${SubscriberNotificationRunLimit.MAX_CONCURRENT_RUNS_PER_JOB} runs are already sending; leaving the Pending notifications to the runs that follow.`);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
logger.warn(`${jobName}: could not take a run permit (${err instanceof Error ? err.message : String(err)}); running without one.`);
|
|
107
|
+
permit = null;
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
await run();
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
if (permit) {
|
|
114
|
+
try {
|
|
115
|
+
await Semaphore.releasePermit(permit);
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
// It lapses on its own after PERMIT_TTL_IN_MS.
|
|
119
|
+
logger.warn(`${jobName}: could not release its run permit: ${err instanceof Error ? err.message : String(err)}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
SubscriberNotificationRunLimit.MAX_CONCURRENT_RUNS_PER_JOB = parsePositiveIntFromEnv("SUBSCRIBER_NOTIFICATION_MAX_CONCURRENT_RUNS_PER_JOB", 4);
|
|
126
|
+
/*
|
|
127
|
+
* Fewer than the runs of a job, so a project whose sends are slow always
|
|
128
|
+
* leaves some of them to everyone else.
|
|
129
|
+
*/
|
|
130
|
+
SubscriberNotificationRunLimit.MAX_CONCURRENT_SENDS_PER_PROJECT = Math.max(1, Math.min(parsePositiveIntFromEnv("SUBSCRIBER_NOTIFICATION_MAX_CONCURRENT_SENDS_PER_PROJECT", 2), SubscriberNotificationRunLimit.MAX_CONCURRENT_RUNS_PER_JOB - 1));
|
|
131
|
+
// The per-project slots' Redis namespace; the key is the job and the project.
|
|
132
|
+
SubscriberNotificationRunLimit.PROJECT_NAMESPACE = "subscriber-notification-project";
|
|
133
|
+
SubscriberNotificationRunLimit.PERMIT_TTL_IN_MS = OneUptimeDate.convertMinutesToMilliseconds(2);
|
|
134
|
+
// The permits' Redis namespace; the key is the job's name.
|
|
135
|
+
SubscriberNotificationRunLimit.NAMESPACE = "subscriber-notification-run";
|
|
136
|
+
export default SubscriberNotificationRunLimit;
|
|
137
|
+
//# sourceMappingURL=SubscriberNotificationRunLimit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SubscriberNotificationRunLimit.js","sourceRoot":"","sources":["../../../../../Server/Utils/StatusPage/SubscriberNotificationRunLimit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import LIMIT_MAX from "../../../Types/Database/LimitMax";
|
|
2
|
+
import OneUptimeDate from "../../../Types/Date";
|
|
3
|
+
/*
|
|
4
|
+
* How long the incident and episode subscriber jobs may spend sending, and
|
|
5
|
+
* how long a notification may sit In progress before it is taken to have been
|
|
6
|
+
* interrupted.
|
|
7
|
+
*
|
|
8
|
+
* A job sends each notification - an incident created, a state change, a
|
|
9
|
+
* public note, a postmortem, or an episode's - by awaiting every message at a
|
|
10
|
+
* bounded concurrency, so a send takes as long as its slowest subscribers
|
|
11
|
+
* rather than returning the moment the messages are queued. These numbers
|
|
12
|
+
* keep that bounded, and tie the job's timeout, a notification's send window
|
|
13
|
+
* and the sweeper's threshold together so they cannot drift apart:
|
|
14
|
+
*
|
|
15
|
+
* - SEND_CONCURRENCY messages are in flight at once, per notification.
|
|
16
|
+
* - SUBSCRIBERS_PER_READ subscribers are read from a status page at a time:
|
|
17
|
+
* LIMIT_MAX, the cap one read used to stop at silently. A page with more is
|
|
18
|
+
* read in batches (SubscriberNotificationFanOut).
|
|
19
|
+
* - A message still unanswered after SEND_TIMEOUT_IN_MS is counted failed and
|
|
20
|
+
* its slot freed. That is longer than any sender's own retries can take -
|
|
21
|
+
* the Notification service tries SMTP three times with a 60 second connect
|
|
22
|
+
* timeout each (about 3 minutes 5 seconds), and the Slack and webhook
|
|
23
|
+
* senders retry three times with 2, 4 and 8 second backoffs - so it only
|
|
24
|
+
* cuts off a request that hangs. The request itself is not cancelled.
|
|
25
|
+
* - A notification starts no new message once SEND_WINDOW_IN_MS has passed
|
|
26
|
+
* since it was claimed. What it has not reached is recorded as not sent,
|
|
27
|
+
* and the notification settles as Failed.
|
|
28
|
+
*
|
|
29
|
+
* The math, for one full read of a status page: 10,000 subscribers at 20 in
|
|
30
|
+
* flight is 500 rounds, and 20 minutes over 500 rounds allows 2.4 seconds a
|
|
31
|
+
* message on average. An email, SMS or webhook through the Notification
|
|
32
|
+
* service normally answers in well under a second, so a 10,000-subscriber
|
|
33
|
+
* page fits with room to spare, and at half a second a message one
|
|
34
|
+
* notification reaches about 48,000 subscribers in its window.
|
|
35
|
+
*
|
|
36
|
+
* - One notification can therefore take at most NOTIFICATION_MAX_IN_MS: its
|
|
37
|
+
* send window, plus the timeout of the messages still in flight when the
|
|
38
|
+
* window closes, plus a minute for the feed item and the status write.
|
|
39
|
+
* - JOB_TIMEOUT_IN_MS is each job's RunCron timeoutInMS. A run claims a new
|
|
40
|
+
* notification only while that notification could still finish inside the
|
|
41
|
+
* job's timeout (LATEST_CLAIM_IN_MS into the run), so a run never outlives
|
|
42
|
+
* its timeout: a timed-out queue job is only stopped being waited for,
|
|
43
|
+
* never cancelled, and would otherwise carry on sending untracked. The
|
|
44
|
+
* notifications it leaves Pending are claimed by the runs that follow, one
|
|
45
|
+
* a minute, which can run alongside it: a claim is atomic
|
|
46
|
+
* (SubscriberNotificationClaim), so each notification is sent once. How
|
|
47
|
+
* many runs of a job may be sending at once, across every worker, is
|
|
48
|
+
* bounded too (SubscriberNotificationRunLimit), so slow sends cannot fill
|
|
49
|
+
* the Worker queue every other cron shares.
|
|
50
|
+
* - STUCK_AFTER_IN_MS is when the sweeper
|
|
51
|
+
* (StatusPageSubscriber:TimeoutStuckNotifications) takes a notification
|
|
52
|
+
* still In progress to have been interrupted - its worker crashed, was
|
|
53
|
+
* redeployed or lost its database connection - and marks it Failed. A
|
|
54
|
+
* running send is never In progress that long, so the margin over the job
|
|
55
|
+
* timeout only has to cover clock skew between the worker and the database.
|
|
56
|
+
*/
|
|
57
|
+
class SubscriberNotificationTiming {
|
|
58
|
+
// The clock of one job run, started when the run starts.
|
|
59
|
+
static startRun(now) {
|
|
60
|
+
return new SubscriberNotificationRunClock(now !== null && now !== void 0 ? now : Date.now());
|
|
61
|
+
}
|
|
62
|
+
// The send window of one notification, started when it is claimed.
|
|
63
|
+
static startSendWindow(now) {
|
|
64
|
+
return new SubscriberNotificationSendWindow((now !== null && now !== void 0 ? now : Date.now()) + SubscriberNotificationTiming.SEND_WINDOW_IN_MS);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
SubscriberNotificationTiming.SEND_CONCURRENCY = 20;
|
|
68
|
+
SubscriberNotificationTiming.SUBSCRIBERS_PER_READ = LIMIT_MAX;
|
|
69
|
+
SubscriberNotificationTiming.SEND_TIMEOUT_IN_MS = OneUptimeDate.convertMinutesToMilliseconds(4);
|
|
70
|
+
SubscriberNotificationTiming.SEND_WINDOW_IN_MS = OneUptimeDate.convertMinutesToMilliseconds(20);
|
|
71
|
+
SubscriberNotificationTiming.SETTLE_ALLOWANCE_IN_MS = OneUptimeDate.convertMinutesToMilliseconds(1);
|
|
72
|
+
SubscriberNotificationTiming.NOTIFICATION_MAX_IN_MS = SubscriberNotificationTiming.SEND_WINDOW_IN_MS +
|
|
73
|
+
SubscriberNotificationTiming.SEND_TIMEOUT_IN_MS +
|
|
74
|
+
SubscriberNotificationTiming.SETTLE_ALLOWANCE_IN_MS;
|
|
75
|
+
SubscriberNotificationTiming.JOB_TIMEOUT_IN_MS = OneUptimeDate.convertMinutesToMilliseconds(30);
|
|
76
|
+
SubscriberNotificationTiming.LATEST_CLAIM_IN_MS = SubscriberNotificationTiming.JOB_TIMEOUT_IN_MS -
|
|
77
|
+
SubscriberNotificationTiming.NOTIFICATION_MAX_IN_MS;
|
|
78
|
+
SubscriberNotificationTiming.STUCK_AFTER_IN_MS = SubscriberNotificationTiming.JOB_TIMEOUT_IN_MS +
|
|
79
|
+
OneUptimeDate.convertMinutesToMilliseconds(10);
|
|
80
|
+
export default SubscriberNotificationTiming;
|
|
81
|
+
export class SubscriberNotificationRunClock {
|
|
82
|
+
constructor(startedAt) {
|
|
83
|
+
this.startedAt = startedAt;
|
|
84
|
+
}
|
|
85
|
+
/*
|
|
86
|
+
* Whether this run may still claim a notification: one claimed now could
|
|
87
|
+
* finish before the run's timeout.
|
|
88
|
+
*/
|
|
89
|
+
canClaimAnotherNotification(now) {
|
|
90
|
+
return ((now !== null && now !== void 0 ? now : Date.now()) - this.startedAt <=
|
|
91
|
+
SubscriberNotificationTiming.LATEST_CLAIM_IN_MS);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
export class SubscriberNotificationSendWindow {
|
|
95
|
+
constructor(closesAt) {
|
|
96
|
+
this.closesAt = closesAt;
|
|
97
|
+
}
|
|
98
|
+
// Whether a new message may still be started.
|
|
99
|
+
isOpen(now) {
|
|
100
|
+
return (now !== null && now !== void 0 ? now : Date.now()) < this.closesAt;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=SubscriberNotificationTiming.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SubscriberNotificationTiming.js","sourceRoot":"","sources":["../../../../../Server/Utils/StatusPage/SubscriberNotificationTiming.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,kCAAkC,CAAC;AACzD,OAAO,aAAa,MAAM,qBAAqB,CAAC;AAEhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,MAAqB,4BAA4B;IA8B/C,yDAAyD;IAClD,MAAM,CAAC,QAAQ,CAAC,GAAY;QACjC,OAAO,IAAI,8BAA8B,CAAC,GAAG,aAAH,GAAG,cAAH,GAAG,GAAI,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IAC/D,CAAC;IAED,mEAAmE;IAC5D,MAAM,CAAC,eAAe,CAC3B,GAAY;QAEZ,OAAO,IAAI,gCAAgC,CACzC,CAAC,GAAG,aAAH,GAAG,cAAH,GAAG,GAAI,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,4BAA4B,CAAC,iBAAiB,CACrE,CAAC;IACJ,CAAC;;AAzCsB,6CAAgB,GAAW,EAAE,CAAC;AAE9B,iDAAoB,GAAW,SAAS,CAAC;AAEzC,+CAAkB,GACvC,aAAa,CAAC,4BAA4B,CAAC,CAAC,CAAC,CAAC;AAEzB,8CAAiB,GACtC,aAAa,CAAC,4BAA4B,CAAC,EAAE,CAAC,CAAC;AAE1B,mDAAsB,GAC3C,aAAa,CAAC,4BAA4B,CAAC,CAAC,CAAC,CAAC;AAEzB,mDAAsB,GAC3C,4BAA4B,CAAC,iBAAiB;IAC9C,4BAA4B,CAAC,kBAAkB;IAC/C,4BAA4B,CAAC,sBAAsB,CAAC;AAE/B,8CAAiB,GACtC,aAAa,CAAC,4BAA4B,CAAC,EAAE,CAAC,CAAC;AAE1B,+CAAkB,GACvC,4BAA4B,CAAC,iBAAiB;IAC9C,4BAA4B,CAAC,sBAAsB,CAAC;AAE/B,8CAAiB,GACtC,4BAA4B,CAAC,iBAAiB;IAC9C,aAAa,CAAC,4BAA4B,CAAC,EAAE,CAAC,CAAC;eA5B9B,4BAA4B;AA6CjD,MAAM,OAAO,8BAA8B;IAGzC,YAAmB,SAAiB;QAClC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;IAED;;;OAGG;IACI,2BAA2B,CAAC,GAAY;QAC7C,OAAO,CACL,CAAC,GAAG,aAAH,GAAG,cAAH,GAAG,GAAI,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS;YACpC,4BAA4B,CAAC,kBAAkB,CAChD,CAAC;IACJ,CAAC;CACF;AAED,MAAM,OAAO,gCAAgC;IAG3C,YAAmB,QAAgB;QACjC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;IAED,8CAA8C;IACvC,MAAM,CAAC,GAAY;QACxB,OAAO,CAAC,GAAG,aAAH,GAAG,cAAH,GAAG,GAAI,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC;IAC7C,CAAC;CACF"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import Incident from "../../../Models/DatabaseModels/Incident";
|
|
2
|
+
import IncidentCustomField from "../../../Models/DatabaseModels/IncidentCustomField";
|
|
3
|
+
import DatabaseCommonInteractionPropsUtil, { PermissionType, } from "../../../Types/BaseDatabase/DatabaseCommonInteractionPropsUtil";
|
|
4
|
+
import { CUSTOM_FIELD_TEMPLATE_VARIABLE_PREFIX } from "../../../Types/CustomField/CustomFieldVariableKey";
|
|
5
|
+
import PermissionScope from "../../../Types/Database/AccessControl/PermissionScope";
|
|
6
|
+
import NotAuthorizedException from "../../../Types/Exception/NotAuthorizedException";
|
|
7
|
+
import Permission, { PermissionHelper, } from "../../../Types/Permission";
|
|
8
|
+
export default class SubscriberTemplateIncidentRecordAccess {
|
|
9
|
+
/**
|
|
10
|
+
* Refuses a write that places these placeholders (from
|
|
11
|
+
* getIncidentRecordPlaceholders) unless the caller may read what they
|
|
12
|
+
* read, for every incident of the project. Does nothing when there are
|
|
13
|
+
* none, or for root and master admin.
|
|
14
|
+
*/
|
|
15
|
+
static assertCanPlace(data) {
|
|
16
|
+
if (data.placeholders.length === 0 ||
|
|
17
|
+
data.props.isRoot ||
|
|
18
|
+
data.props.isMasterAdmin) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const unrestricted = SubscriberTemplateIncidentRecordAccess.getUnrestrictedPermissions(data.props);
|
|
22
|
+
const blocked = DatabaseCommonInteractionPropsUtil.getUserPermissions(data.props, PermissionType.Block).map((row) => {
|
|
23
|
+
return row.permission;
|
|
24
|
+
});
|
|
25
|
+
for (const requirement of SubscriberTemplateIncidentRecordAccess.getRequirements(data.placeholders)) {
|
|
26
|
+
const isGranted = PermissionHelper.doesPermissionsIntersect(unrestricted, requirement.permissions);
|
|
27
|
+
const isBlocked = PermissionHelper.doesPermissionsIntersect(blocked, requirement.permissions);
|
|
28
|
+
if (isGranted && !isBlocked) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const placeholders = data.placeholders
|
|
32
|
+
.map((name) => {
|
|
33
|
+
return `{{${name}}}`;
|
|
34
|
+
})
|
|
35
|
+
.join(", ");
|
|
36
|
+
throw new NotAuthorizedException(`Placing ${placeholders} in a subscriber notification template needs permission to ${requirement.description} for every incident in the project: these values come from your team's incident records, which the status page does not show. You need one of these permissions, not limited to some labels or to incidents you own: ${PermissionHelper.getPermissionTitles(requirement.permissions).join(", ")}.`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/*
|
|
40
|
+
* What these placeholders need: reading incidents, and the column each
|
|
41
|
+
* one reads - Incident.labels for {{incidentLabels}}; Incident.customFields
|
|
42
|
+
* and the field definitions for {{customFields.<key>}}.
|
|
43
|
+
*/
|
|
44
|
+
static getRequirements(placeholders) {
|
|
45
|
+
const requirements = [
|
|
46
|
+
{
|
|
47
|
+
description: "read incidents",
|
|
48
|
+
permissions: this.getTableReadPermissions(Incident),
|
|
49
|
+
},
|
|
50
|
+
];
|
|
51
|
+
if (placeholders.some((name) => {
|
|
52
|
+
return name.startsWith(CUSTOM_FIELD_TEMPLATE_VARIABLE_PREFIX);
|
|
53
|
+
})) {
|
|
54
|
+
requirements.push({
|
|
55
|
+
description: "read incident custom field values",
|
|
56
|
+
permissions: this.getColumnReadPermissions(Incident, "customFields"),
|
|
57
|
+
}, {
|
|
58
|
+
description: "read incident custom fields",
|
|
59
|
+
permissions: this.getTableReadPermissions(IncidentCustomField),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if (placeholders.some((name) => {
|
|
63
|
+
return !name.startsWith(CUSTOM_FIELD_TEMPLATE_VARIABLE_PREFIX);
|
|
64
|
+
})) {
|
|
65
|
+
requirements.push({
|
|
66
|
+
description: "read incident labels",
|
|
67
|
+
permissions: this.getColumnReadPermissions(Incident, "labels"),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return requirements;
|
|
71
|
+
}
|
|
72
|
+
/*
|
|
73
|
+
* The caller's granted permissions that reach every record, read as the
|
|
74
|
+
* read checks read them (PermissionHelper.getNonAccessControlPermissions,
|
|
75
|
+
* OwnedScopePermission): a row scoped to All, or a Labels (or legacy
|
|
76
|
+
* unscoped) row with no labels. A row limited to labels does not, nor one
|
|
77
|
+
* limited to owned records - except for a project-wide role that cannot
|
|
78
|
+
* be scoped (Project Owner, Project Admin), whose stray Owned scope the
|
|
79
|
+
* read checks ignore too.
|
|
80
|
+
*/
|
|
81
|
+
static getUnrestrictedPermissions(props) {
|
|
82
|
+
return DatabaseCommonInteractionPropsUtil.getUserPermissions(props, PermissionType.Allow)
|
|
83
|
+
.filter((row) => {
|
|
84
|
+
if (row.scope === PermissionScope.All) {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
if (row.scope === PermissionScope.Owned) {
|
|
88
|
+
return !PermissionHelper.isScopeApplicable(row.permission);
|
|
89
|
+
}
|
|
90
|
+
return !row.labelIds || row.labelIds.length === 0;
|
|
91
|
+
})
|
|
92
|
+
.map((row) => {
|
|
93
|
+
return row.permission;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
// As the table check reads them, the operational resource wildcard included.
|
|
97
|
+
static getTableReadPermissions(modelType) {
|
|
98
|
+
const model = new modelType();
|
|
99
|
+
const permissions = [...model.readRecordPermissions];
|
|
100
|
+
if (model.isOperationalResource &&
|
|
101
|
+
!permissions.includes(Permission.ReadAllOperationalResources)) {
|
|
102
|
+
permissions.push(Permission.ReadAllOperationalResources);
|
|
103
|
+
}
|
|
104
|
+
return permissions;
|
|
105
|
+
}
|
|
106
|
+
// As the select check reads them: no wildcard reaches a column.
|
|
107
|
+
static getColumnReadPermissions(modelType, columnName) {
|
|
108
|
+
var _a;
|
|
109
|
+
return [
|
|
110
|
+
...(((_a = new modelType().getColumnAccessControlFor(columnName)) === null || _a === void 0 ? void 0 : _a.read) || []),
|
|
111
|
+
];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=SubscriberTemplateIncidentRecordAccess.js.map
|