@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.
Files changed (440) hide show
  1. package/Models/DatabaseModels/Incident.ts +267 -1
  2. package/Models/DatabaseModels/IncidentCustomField.ts +202 -0
  3. package/Models/DatabaseModels/IncidentEpisode.ts +30 -0
  4. package/Models/DatabaseModels/IncidentTemplate.ts +126 -1
  5. package/Models/DatabaseModels/StatusPage.ts +55 -0
  6. package/Models/DatabaseModels/StatusPageSubscriber.ts +128 -0
  7. package/Server/API/IncidentAPI.ts +162 -0
  8. package/Server/API/StatusPageAPI.ts +487 -171
  9. package/Server/API/StatusPageSubscriberAPI.ts +63 -15
  10. package/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.ts +49 -0
  11. package/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.ts +108 -0
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.ts +33 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.ts +51 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.ts +33 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.ts +168 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.ts +44 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +14 -0
  18. package/Server/Infrastructure/Semaphore.ts +10 -0
  19. package/Server/Middleware/IdentityRateLimit.ts +108 -3
  20. package/Server/Middleware/SubscriberNotificationPreviewRateLimit.ts +237 -0
  21. package/Server/Middleware/SubscriberNotificationTestSendRateLimit.ts +304 -0
  22. package/Server/Services/DatabaseService.ts +93 -10
  23. package/Server/Services/IncidentCustomFieldService.ts +276 -1
  24. package/Server/Services/IncidentEpisodePublicNoteService.ts +28 -0
  25. package/Server/Services/IncidentEpisodeService.ts +15 -0
  26. package/Server/Services/IncidentEpisodeStateTimelineService.ts +27 -1
  27. package/Server/Services/IncidentPublicNoteService.ts +30 -1
  28. package/Server/Services/IncidentService.ts +1535 -65
  29. package/Server/Services/IncidentStateTimelineService.ts +13 -0
  30. package/Server/Services/IncidentTemplateService.ts +109 -0
  31. package/Server/Services/ScheduledMaintenancePublicNoteService.ts +30 -1
  32. package/Server/Services/ScheduledMaintenanceService.ts +68 -18
  33. package/Server/Services/SmsService.ts +10 -0
  34. package/Server/Services/StatusPageService.ts +65 -8
  35. package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +299 -37
  36. package/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.ts +129 -0
  37. package/Server/Services/StatusPageSubscriberService.ts +1161 -64
  38. package/Server/Types/Markdown.ts +153 -6
  39. package/Server/Types/Workflow/Components/Email.ts +61 -2
  40. package/Server/Utils/CustomField/CustomFieldRename.ts +378 -0
  41. package/Server/Utils/DataSource/EgressGuard.ts +66 -21
  42. package/Server/Utils/Mail/PinnedSmtpSocket.ts +130 -0
  43. package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +10 -0
  44. package/Server/Utils/SSRFProtection.ts +76 -16
  45. package/Server/Utils/StatusPage/IncidentStatusPageScope.ts +948 -0
  46. package/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.ts +656 -0
  47. package/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.ts +805 -0
  48. package/Server/Utils/StatusPage/StatusPageExclusion.ts +18 -0
  49. package/Server/Utils/StatusPage/StatusPageReadAccess.ts +125 -0
  50. package/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.ts +151 -0
  51. package/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.ts +70 -0
  52. package/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.ts +421 -0
  53. package/Server/Utils/StatusPage/SubscriberNotificationClaim.ts +107 -0
  54. package/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.ts +1049 -0
  55. package/Server/Utils/StatusPage/SubscriberNotificationFanOut.ts +180 -0
  56. package/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.ts +839 -0
  57. package/Server/Utils/StatusPage/SubscriberNotificationResendAccess.ts +393 -0
  58. package/Server/Utils/StatusPage/SubscriberNotificationRunLimit.ts +246 -0
  59. package/Server/Utils/StatusPage/SubscriberNotificationTiming.ts +133 -0
  60. package/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.ts +215 -0
  61. package/Server/Utils/StatusPageResource.ts +51 -20
  62. package/Server/Utils/VM/VMAPI.ts +1 -0
  63. package/Server/Utils/VM/VMRunner.ts +10 -0
  64. package/Server/Utils/Workflow/OAuth2TokenClient.ts +10 -0
  65. package/Server/Views/Partials/SensitiveUrlToken.ejs +8 -5
  66. package/Tests/App/Accounts/RegisterVerifyEmail.test.tsx +748 -0
  67. package/Tests/App/Accounts/ResendVerificationEmail.test.tsx +1222 -0
  68. package/Tests/App/Accounts/VerificationEmailResendUtils.test.ts +797 -0
  69. package/Tests/App/Accounts/VerifyEmailPending.test.tsx +538 -0
  70. package/Tests/App/Accounts/VerifyEmailResend.test.tsx +779 -0
  71. package/Tests/App/Dashboard/CustomFieldsSettingsIncidentOptions.test.tsx +357 -0
  72. package/Tests/App/Dashboard/EventNotes.test.tsx +776 -0
  73. package/Tests/App/Dashboard/EventNotesPages.test.tsx +119 -0
  74. package/Tests/App/Dashboard/EventNotesUtil.test.ts +116 -17
  75. package/Tests/App/Dashboard/EventOverviewPages.test.tsx +242 -5
  76. package/Tests/App/Dashboard/IncidentChangeStateNotifyDefault.test.tsx +91 -1
  77. package/Tests/App/Dashboard/IncidentCreateDetailsStep.test.tsx +643 -0
  78. package/Tests/App/Dashboard/IncidentCreateFromAlerts.test.tsx +8 -1
  79. package/Tests/App/Dashboard/IncidentCreateStatusPageScope.test.tsx +601 -0
  80. package/Tests/App/Dashboard/IncidentCustomFieldTemplateVariables.test.tsx +386 -0
  81. package/Tests/App/Dashboard/IncidentNoteTemplatePlaceholders.test.tsx +124 -0
  82. package/Tests/App/Dashboard/IncidentNoteTemplateVariablesLoader.test.ts +305 -0
  83. package/Tests/App/Dashboard/IncidentOverviewNotifyDefault.test.tsx +112 -0
  84. package/Tests/App/Dashboard/IncidentSettingsRenotifyOnPublish.test.tsx +674 -0
  85. package/Tests/App/Dashboard/IncidentSettingsStatusPageScope.test.tsx +1051 -0
  86. package/Tests/App/Dashboard/IncidentTemplateCustomFields.test.tsx +384 -0
  87. package/Tests/App/Dashboard/IncidentTemplateStatusPageScopeCard.test.tsx +215 -0
  88. package/Tests/App/Dashboard/PublicNotePagesNotifyDefault.test.tsx +113 -1
  89. package/Tests/App/Dashboard/SubscriberAudienceSummary.test.tsx +512 -0
  90. package/Tests/App/Dashboard/SubscriberNotificationPreviewModal.test.tsx +837 -0
  91. package/Tests/App/Dashboard/SubscriberNotificationStatusLabel.test.tsx +88 -0
  92. package/Tests/App/Dashboard/SubscriberNotificationStatusResend.test.tsx +473 -0
  93. package/Tests/App/Dashboard/SubscriberTemplateLivePreview.test.tsx +196 -0
  94. package/Tests/App/Dashboard/TeamAddedSubscribersUnsubscribedNotice.test.tsx +248 -0
  95. package/Tests/App/SensitiveUrlTokenBootstrap.test.ts +64 -3
  96. package/Tests/App/StatusPage/StatusPageUnsubscribePage.test.tsx +412 -0
  97. package/Tests/Models/IncidentCustomFieldApiSchemaContract.test.ts +217 -0
  98. package/Tests/Models/IncidentCustomFieldCreateSettingsColumns.test.ts +252 -0
  99. package/Tests/Models/StatusPageSubscriberUnsubscribeColumns.test.ts +218 -0
  100. package/Tests/Server/API/ExpiredSessionRouteGuards.test.ts +7 -0
  101. package/Tests/Server/API/IncidentSubscriberAudienceAPI.test.ts +1408 -0
  102. package/Tests/Server/API/StatusPageIncidentScope.test.ts +1868 -0
  103. package/Tests/Server/API/StatusPageIncidentScopePostgres.test.ts +790 -0
  104. package/Tests/Server/API/StatusPageManageSubscriptionRecipient.test.ts +10 -1
  105. package/Tests/Server/API/StatusPageManageSubscriptionTemplateVariables.test.ts +206 -18
  106. package/Tests/Server/API/StatusPagePostmortemVisibility.test.ts +14 -2
  107. package/Tests/Server/API/StatusPageSubscriberCreateResponse.test.ts +217 -0
  108. package/Tests/Server/API/StatusPageSubscriberOwnership.test.ts +103 -2
  109. package/Tests/Server/API/StatusPageUnsubscribeAPI.test.ts +396 -0
  110. package/Tests/Server/Infrastructure/Postgres/AddIncidentStatusPageScopeMigration.test.ts +395 -0
  111. package/Tests/Server/Infrastructure/Postgres/AddIncidentTemplateStatusPageScopeFlagMigration.test.ts +150 -0
  112. package/Tests/Server/Infrastructure/Postgres/AddSubscriberNotificationClaimedAtMigration.test.ts +156 -0
  113. package/Tests/Server/Infrastructure/Postgres/SkipHiddenIncidentCreatedNotificationsMigration.test.ts +410 -0
  114. package/Tests/Server/Middleware/IdentityRateLimit.test.ts +551 -1
  115. package/Tests/Server/Middleware/SubscriberNotificationPreviewRateLimit.test.ts +238 -0
  116. package/Tests/Server/Middleware/SubscriberNotificationTestSendRateLimit.test.ts +245 -0
  117. package/Tests/Server/Services/AddStatusPageSubscriberUnsubscribeTokenMigration.test.ts +152 -0
  118. package/Tests/Server/Services/CrossProjectRelationWriteGuard.test.ts +221 -36
  119. package/Tests/Server/Services/DatabaseServiceCompareAndSetColumns.test.ts +191 -0
  120. package/Tests/Server/Services/IncidentCreatedRenotifyOnPublish.test.ts +681 -0
  121. package/Tests/Server/Services/IncidentCreatedResend.test.ts +775 -0
  122. package/Tests/Server/Services/IncidentCustomFieldRenamePostgres.test.ts +713 -0
  123. package/Tests/Server/Services/IncidentCustomFieldService.test.ts +751 -0
  124. package/Tests/Server/Services/IncidentCustomFieldValueValidation.test.ts +556 -0
  125. package/Tests/Server/Services/IncidentStatusPageScope.test.ts +2971 -0
  126. package/Tests/Server/Services/IncidentStatusPageScopeFeed.test.ts +131 -0
  127. package/Tests/Server/Services/IncidentStatusPageScopePostgres.test.ts +625 -0
  128. package/Tests/Server/Services/IncidentTemplateCustomFieldsCopy.test.ts +311 -0
  129. package/Tests/Server/Services/IncidentTemplateStatusPageScope.test.ts +363 -0
  130. package/Tests/Server/Services/PublicNoteEditFeedEventType.test.ts +213 -0
  131. package/Tests/Server/Services/PublicNoteResendAccess.test.ts +994 -0
  132. package/Tests/Server/Services/ScheduledMaintenanceSubscriberTemplateFormats.test.ts +289 -9
  133. package/Tests/Server/Services/SmsServiceFailIfNotSent.test.ts +67 -0
  134. package/Tests/Server/Services/StatusPageReportIncidentCounts.test.ts +610 -0
  135. package/Tests/Server/Services/StatusPageSubscriberAudienceCounts.test.ts +201 -0
  136. package/Tests/Server/Services/StatusPageSubscriberAudienceCountsPostgres.test.ts +271 -0
  137. package/Tests/Server/Services/StatusPageSubscriberConfirmationTemplateEscaping.test.ts +200 -0
  138. package/Tests/Server/Services/StatusPageSubscriberNotificationLogRedaction.test.ts +54 -0
  139. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateCompile.test.ts +37 -0
  140. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateIncidentRecordAccess.test.ts +772 -0
  141. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateLookup.test.ts +506 -0
  142. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateLookupPostgres.test.ts +261 -0
  143. package/Tests/Server/Services/StatusPageSubscriberReport.test.ts +21 -5
  144. package/Tests/Server/Services/StatusPageSubscriberUnsubscribe.test.ts +1618 -0
  145. package/Tests/Server/Services/StatusPageSubscriberUnsubscribeLinkSenders.test.ts +373 -0
  146. package/Tests/Server/Services/StatusPageSubscriberUnsubscribePostgres.test.ts +388 -0
  147. package/Tests/Server/Services/SubscriberNotificationInFlightRequeue.test.ts +500 -0
  148. package/Tests/Server/Types/MarkdownEmailLinks.test.ts +320 -0
  149. package/Tests/Server/Types/Workflow/Components/Email.test.ts +491 -1
  150. package/Tests/Server/Types/Workflow/Components/EmailSmtpPinning.test.ts +398 -0
  151. package/Tests/Server/Utils/CustomField/CustomFieldRename.test.ts +586 -0
  152. package/Tests/Server/Utils/DataSource/EgressGuard.test.ts +246 -10
  153. package/Tests/Server/Utils/DataSource/EgressGuardDeploymentConfig.test.ts +226 -0
  154. package/Tests/Server/Utils/DataSource/EgressGuardPinning.test.ts +131 -0
  155. package/Tests/Server/Utils/DataSource/HttpFetch.test.ts +4 -0
  156. package/Tests/Server/Utils/EgressPolicyEnvironment.ts +45 -0
  157. package/Tests/Server/Utils/Mail/PinnedSmtpSocket.test.ts +409 -0
  158. package/Tests/Server/Utils/Monitor/MonitorCriteriaObservationBuilderUnits.test.ts +23 -0
  159. package/Tests/Server/Utils/SSRFProtection.test.ts +4 -0
  160. package/Tests/Server/Utils/SSRFProtectionCloudServiceAddresses.test.ts +4 -0
  161. package/Tests/Server/Utils/SSRFProtectionPrivateNetwork.test.ts +4 -0
  162. package/Tests/Server/Utils/SSRFProtectionResolutionDetail.test.ts +314 -0
  163. package/Tests/Server/Utils/StatusPage/IncidentStatusPageScope.test.ts +1699 -0
  164. package/Tests/Server/Utils/StatusPage/IncidentStatusPageScopeQueriesPostgres.test.ts +508 -0
  165. package/Tests/Server/Utils/StatusPage/IncidentSubscriberAudienceCallerPermission.test.ts +246 -0
  166. package/Tests/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.test.ts +1348 -0
  167. package/Tests/Server/Utils/StatusPage/StatusPageReadAccess.test.ts +221 -0
  168. package/Tests/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.test.ts +132 -0
  169. package/Tests/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.test.ts +155 -0
  170. package/Tests/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.test.ts +708 -0
  171. package/Tests/Server/Utils/StatusPage/SubscriberNotificationClaim.test.ts +255 -0
  172. package/Tests/Server/Utils/StatusPage/SubscriberNotificationClaimPostgres.test.ts +528 -0
  173. package/Tests/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.test.ts +1238 -0
  174. package/Tests/Server/Utils/StatusPage/SubscriberNotificationFanOut.test.ts +472 -0
  175. package/Tests/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.test.ts +1168 -0
  176. package/Tests/Server/Utils/StatusPage/SubscriberNotificationRunLimit.test.ts +275 -0
  177. package/Tests/Server/Utils/StatusPage/SubscriberNotificationTiming.test.ts +131 -0
  178. package/Tests/Server/Utils/StatusPageResource.test.ts +98 -0
  179. package/Tests/Server/Utils/VM/VMRunnerPrivateNetworkWiring.test.ts +17 -0
  180. package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +75 -0
  181. package/Tests/Types/CustomField/CustomFieldSavedViews.test.ts +247 -0
  182. package/Tests/Types/CustomField/CustomFieldTemplateValues.test.ts +186 -0
  183. package/Tests/Types/CustomField/CustomFieldTypeCorrespondence.test.ts +48 -0
  184. package/Tests/Types/CustomField/CustomFieldValueFormat.test.ts +215 -0
  185. package/Tests/Types/CustomField/CustomFieldValueValidator.test.ts +343 -0
  186. package/Tests/Types/CustomField/CustomFieldVariableKey.test.ts +364 -0
  187. package/Tests/Types/SafeHtml.test.ts +102 -0
  188. package/Tests/Types/StatusPage/IncidentCreatedRenotify.test.ts +419 -0
  189. package/Tests/Types/StatusPage/IncidentCreatedResend.test.ts +118 -0
  190. package/Tests/Types/StatusPage/IncidentScopeAddedPagesNotification.test.ts +558 -0
  191. package/Tests/Types/StatusPage/IncidentSubscriberAudience.test.ts +284 -0
  192. package/Tests/Types/StatusPage/StatusPageSubscriberUnsubscribe.test.ts +341 -0
  193. package/Tests/Types/StatusPage/SubscriberNotificationPreview.test.ts +196 -0
  194. package/Tests/Types/StatusPage/SubscriberNotificationResend.test.ts +201 -0
  195. package/Tests/Types/StatusPage/SubscriberNotificationTemplateCompiler.test.ts +277 -0
  196. package/Tests/Types/StatusPage/SubscriberNotificationTemplatePreview.test.ts +179 -0
  197. package/Tests/Types/StatusPage/SubscriberNotificationTemplateVariables.test.ts +324 -0
  198. package/Tests/Types/StatusPage/SubscriberUpdateNotification.test.ts +60 -13
  199. package/Tests/UI/Components/CustomFields/CustomFieldFormFields.test.ts +432 -0
  200. package/Tests/UI/Components/CustomFields/CustomFieldModelFormFields.test.tsx +685 -0
  201. package/Tests/UI/Components/CustomFields/CustomFieldsDetailOrder.test.tsx +469 -0
  202. package/Tests/UI/Components/Forms/ModelFormCustomFieldInputs.test.tsx +325 -0
  203. package/Tests/UI/Components/Forms/ModelFormDropdownOptionsRefused.test.tsx +358 -0
  204. package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +22 -0
  205. package/Tests/UI/Monitor/NetworkPathView.test.tsx +289 -0
  206. package/Tests/Utils/Incident/IncidentNoteTemplateVariables.test.ts +623 -0
  207. package/Tests/Utils/Markdown/MarkdownEscape.test.ts +89 -0
  208. package/Tests/Utils/ProbeNetworkFailureUtil.test.ts +992 -0
  209. package/Tests/Utils/RequestFailedDetails.test.ts +575 -0
  210. package/Types/CustomField/CustomFieldOrder.ts +55 -0
  211. package/Types/CustomField/CustomFieldSavedViews.ts +225 -0
  212. package/Types/CustomField/CustomFieldTemplateMerge.ts +71 -0
  213. package/Types/CustomField/CustomFieldType.ts +14 -0
  214. package/Types/CustomField/CustomFieldValueFormat.ts +161 -0
  215. package/Types/CustomField/CustomFieldValueValidator.ts +488 -0
  216. package/Types/CustomField/CustomFieldVariableKey.ts +211 -0
  217. package/Types/SafeHtml.ts +93 -0
  218. package/Types/StatusPage/IncidentCreatedRenotify.ts +224 -0
  219. package/Types/StatusPage/IncidentCreatedResend.ts +108 -0
  220. package/Types/StatusPage/IncidentScopeAddedPagesNotification.ts +343 -0
  221. package/Types/StatusPage/IncidentSubscriberAudience.ts +332 -0
  222. package/Types/StatusPage/StatusPageSubscriberUnsubscribe.ts +405 -0
  223. package/Types/StatusPage/SubscriberNotificationInterruption.ts +32 -0
  224. package/Types/StatusPage/SubscriberNotificationPreview.ts +322 -0
  225. package/Types/StatusPage/SubscriberNotificationResend.ts +132 -0
  226. package/Types/StatusPage/SubscriberNotificationTemplateCompiler.ts +180 -0
  227. package/Types/StatusPage/SubscriberNotificationTemplatePreview.ts +301 -0
  228. package/Types/StatusPage/SubscriberNotificationTemplateVariables.ts +217 -4
  229. package/Types/StatusPage/SubscriberUpdateNotification.ts +32 -11
  230. package/UI/Components/CustomFields/CustomFieldFormFields.ts +317 -0
  231. package/UI/Components/CustomFields/CustomFieldModelFormFields.tsx +395 -0
  232. package/UI/Components/CustomFields/CustomFieldsDetail.tsx +95 -55
  233. package/UI/Components/Forms/ModelForm.tsx +70 -8
  234. package/UI/Components/Forms/Validation.ts +1 -1
  235. package/UI/Components/ModelFormModal/ModelFormModal.tsx +2 -4
  236. package/UI/Components/ModelTable/AnalyticsModelTable.tsx +2 -6
  237. package/UI/Components/ModelTable/BaseModelTable.tsx +9 -8
  238. package/UI/Components/ModelTable/CustomFieldColumns.tsx +14 -1
  239. package/UI/Components/ModelTable/ModelTable.tsx +6 -7
  240. package/UI/Utils/SensitiveUrlToken.ts +10 -5
  241. package/Utils/API.ts +247 -2
  242. package/Utils/Incident/IncidentNoteTemplateVariables.ts +304 -0
  243. package/Utils/Markdown/MarkdownEscape.ts +65 -0
  244. package/Utils/ProbeNetworkFailureUtil.ts +673 -0
  245. package/build/dist/Models/DatabaseModels/Incident.js +267 -1
  246. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  247. package/build/dist/Models/DatabaseModels/IncidentCustomField.js +207 -1
  248. package/build/dist/Models/DatabaseModels/IncidentCustomField.js.map +1 -1
  249. package/build/dist/Models/DatabaseModels/IncidentEpisode.js +31 -0
  250. package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
  251. package/build/dist/Models/DatabaseModels/IncidentTemplate.js +124 -1
  252. package/build/dist/Models/DatabaseModels/IncidentTemplate.js.map +1 -1
  253. package/build/dist/Models/DatabaseModels/StatusPage.js +56 -0
  254. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  255. package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js +131 -0
  256. package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js.map +1 -1
  257. package/build/dist/Server/API/IncidentAPI.js +100 -1
  258. package/build/dist/Server/API/IncidentAPI.js.map +1 -1
  259. package/build/dist/Server/API/StatusPageAPI.js +417 -176
  260. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  261. package/build/dist/Server/API/StatusPageSubscriberAPI.js +52 -13
  262. package/build/dist/Server/API/StatusPageSubscriberAPI.js.map +1 -1
  263. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.js +41 -0
  264. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.js.map +1 -0
  265. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.js +57 -0
  266. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.js.map +1 -0
  267. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.js +23 -0
  268. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.js.map +1 -0
  269. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.js +40 -0
  270. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.js.map +1 -0
  271. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.js +26 -0
  272. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.js.map +1 -0
  273. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.js +89 -0
  274. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.js.map +1 -0
  275. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.js +29 -0
  276. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.js.map +1 -0
  277. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +14 -0
  278. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  279. package/build/dist/Server/Infrastructure/Semaphore.js +3 -0
  280. package/build/dist/Server/Infrastructure/Semaphore.js.map +1 -1
  281. package/build/dist/Server/Middleware/IdentityRateLimit.js +89 -3
  282. package/build/dist/Server/Middleware/IdentityRateLimit.js.map +1 -1
  283. package/build/dist/Server/Middleware/SubscriberNotificationPreviewRateLimit.js +149 -0
  284. package/build/dist/Server/Middleware/SubscriberNotificationPreviewRateLimit.js.map +1 -0
  285. package/build/dist/Server/Middleware/SubscriberNotificationTestSendRateLimit.js +190 -0
  286. package/build/dist/Server/Middleware/SubscriberNotificationTestSendRateLimit.js.map +1 -0
  287. package/build/dist/Server/Services/DatabaseService.js +56 -7
  288. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  289. package/build/dist/Server/Services/IncidentCustomFieldService.js +204 -8
  290. package/build/dist/Server/Services/IncidentCustomFieldService.js.map +1 -1
  291. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js +24 -0
  292. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js.map +1 -1
  293. package/build/dist/Server/Services/IncidentEpisodeService.js +14 -0
  294. package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
  295. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js +26 -0
  296. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js.map +1 -1
  297. package/build/dist/Server/Services/IncidentPublicNoteService.js +26 -1
  298. package/build/dist/Server/Services/IncidentPublicNoteService.js.map +1 -1
  299. package/build/dist/Server/Services/IncidentService.js +1052 -0
  300. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  301. package/build/dist/Server/Services/IncidentStateTimelineService.js +12 -0
  302. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  303. package/build/dist/Server/Services/IncidentTemplateService.js +83 -0
  304. package/build/dist/Server/Services/IncidentTemplateService.js.map +1 -1
  305. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js +26 -1
  306. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js.map +1 -1
  307. package/build/dist/Server/Services/ScheduledMaintenanceService.js +47 -17
  308. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  309. package/build/dist/Server/Services/SmsService.js +2 -22
  310. package/build/dist/Server/Services/SmsService.js.map +1 -1
  311. package/build/dist/Server/Services/StatusPageService.js +47 -9
  312. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  313. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +237 -32
  314. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -1
  315. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.js +109 -0
  316. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.js.map +1 -1
  317. package/build/dist/Server/Services/StatusPageSubscriberService.js +925 -99
  318. package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
  319. package/build/dist/Server/Types/Markdown.js +110 -6
  320. package/build/dist/Server/Types/Markdown.js.map +1 -1
  321. package/build/dist/Server/Types/Workflow/Components/Email.js +55 -6
  322. package/build/dist/Server/Types/Workflow/Components/Email.js.map +1 -1
  323. package/build/dist/Server/Utils/CustomField/CustomFieldRename.js +211 -0
  324. package/build/dist/Server/Utils/CustomField/CustomFieldRename.js.map +1 -0
  325. package/build/dist/Server/Utils/DataSource/EgressGuard.js +41 -12
  326. package/build/dist/Server/Utils/DataSource/EgressGuard.js.map +1 -1
  327. package/build/dist/Server/Utils/Mail/PinnedSmtpSocket.js +101 -0
  328. package/build/dist/Server/Utils/Mail/PinnedSmtpSocket.js.map +1 -0
  329. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +9 -0
  330. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
  331. package/build/dist/Server/Utils/SSRFProtection.js +48 -6
  332. package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
  333. package/build/dist/Server/Utils/StatusPage/IncidentStatusPageScope.js +631 -0
  334. package/build/dist/Server/Utils/StatusPage/IncidentStatusPageScope.js.map +1 -0
  335. package/build/dist/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.js +403 -0
  336. package/build/dist/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.js.map +1 -0
  337. package/build/dist/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.js +451 -0
  338. package/build/dist/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.js.map +1 -0
  339. package/build/dist/Server/Utils/StatusPage/StatusPageExclusion.js +13 -0
  340. package/build/dist/Server/Utils/StatusPage/StatusPageExclusion.js.map +1 -0
  341. package/build/dist/Server/Utils/StatusPage/StatusPageReadAccess.js +91 -0
  342. package/build/dist/Server/Utils/StatusPage/StatusPageReadAccess.js.map +1 -0
  343. package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.js +101 -0
  344. package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.js.map +1 -0
  345. package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.js +47 -0
  346. package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.js.map +1 -0
  347. package/build/dist/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.js +273 -0
  348. package/build/dist/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.js.map +1 -0
  349. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationClaim.js +73 -0
  350. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationClaim.js.map +1 -0
  351. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.js +722 -0
  352. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.js.map +1 -0
  353. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationFanOut.js +102 -0
  354. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationFanOut.js.map +1 -0
  355. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.js +527 -0
  356. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.js.map +1 -0
  357. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationResendAccess.js +278 -0
  358. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationResendAccess.js.map +1 -0
  359. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationRunLimit.js +137 -0
  360. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationRunLimit.js.map +1 -0
  361. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationTiming.js +103 -0
  362. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationTiming.js.map +1 -0
  363. package/build/dist/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.js +114 -0
  364. package/build/dist/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.js.map +1 -0
  365. package/build/dist/Server/Utils/StatusPageResource.js +39 -7
  366. package/build/dist/Server/Utils/StatusPageResource.js.map +1 -1
  367. package/build/dist/Server/Utils/VM/VMAPI.js.map +1 -1
  368. package/build/dist/Server/Utils/VM/VMRunner.js +1 -0
  369. package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
  370. package/build/dist/Server/Utils/Workflow/OAuth2TokenClient.js +3 -0
  371. package/build/dist/Server/Utils/Workflow/OAuth2TokenClient.js.map +1 -1
  372. package/build/dist/Types/CustomField/CustomFieldOrder.js +36 -0
  373. package/build/dist/Types/CustomField/CustomFieldOrder.js.map +1 -0
  374. package/build/dist/Types/CustomField/CustomFieldSavedViews.js +135 -0
  375. package/build/dist/Types/CustomField/CustomFieldSavedViews.js.map +1 -0
  376. package/build/dist/Types/CustomField/CustomFieldTemplateMerge.js +27 -0
  377. package/build/dist/Types/CustomField/CustomFieldTemplateMerge.js.map +1 -0
  378. package/build/dist/Types/CustomField/CustomFieldType.js +12 -0
  379. package/build/dist/Types/CustomField/CustomFieldType.js.map +1 -1
  380. package/build/dist/Types/CustomField/CustomFieldValueFormat.js +126 -0
  381. package/build/dist/Types/CustomField/CustomFieldValueFormat.js.map +1 -0
  382. package/build/dist/Types/CustomField/CustomFieldValueValidator.js +280 -0
  383. package/build/dist/Types/CustomField/CustomFieldValueValidator.js.map +1 -0
  384. package/build/dist/Types/CustomField/CustomFieldVariableKey.js +150 -0
  385. package/build/dist/Types/CustomField/CustomFieldVariableKey.js.map +1 -0
  386. package/build/dist/Types/SafeHtml.js +84 -0
  387. package/build/dist/Types/SafeHtml.js.map +1 -0
  388. package/build/dist/Types/StatusPage/IncidentCreatedRenotify.js +117 -0
  389. package/build/dist/Types/StatusPage/IncidentCreatedRenotify.js.map +1 -0
  390. package/build/dist/Types/StatusPage/IncidentCreatedResend.js +86 -0
  391. package/build/dist/Types/StatusPage/IncidentCreatedResend.js.map +1 -0
  392. package/build/dist/Types/StatusPage/IncidentScopeAddedPagesNotification.js +246 -0
  393. package/build/dist/Types/StatusPage/IncidentScopeAddedPagesNotification.js.map +1 -0
  394. package/build/dist/Types/StatusPage/IncidentSubscriberAudience.js +183 -0
  395. package/build/dist/Types/StatusPage/IncidentSubscriberAudience.js.map +1 -0
  396. package/build/dist/Types/StatusPage/StatusPageSubscriberUnsubscribe.js +277 -0
  397. package/build/dist/Types/StatusPage/StatusPageSubscriberUnsubscribe.js.map +1 -0
  398. package/build/dist/Types/StatusPage/SubscriberNotificationInterruption.js +27 -0
  399. package/build/dist/Types/StatusPage/SubscriberNotificationInterruption.js.map +1 -0
  400. package/build/dist/Types/StatusPage/SubscriberNotificationPreview.js +190 -0
  401. package/build/dist/Types/StatusPage/SubscriberNotificationPreview.js.map +1 -0
  402. package/build/dist/Types/StatusPage/SubscriberNotificationResend.js +102 -0
  403. package/build/dist/Types/StatusPage/SubscriberNotificationResend.js.map +1 -0
  404. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateCompiler.js +97 -0
  405. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateCompiler.js.map +1 -0
  406. package/build/dist/Types/StatusPage/SubscriberNotificationTemplatePreview.js +175 -0
  407. package/build/dist/Types/StatusPage/SubscriberNotificationTemplatePreview.js.map +1 -0
  408. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateVariables.js +144 -4
  409. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateVariables.js.map +1 -1
  410. package/build/dist/Types/StatusPage/SubscriberUpdateNotification.js +29 -9
  411. package/build/dist/Types/StatusPage/SubscriberUpdateNotification.js.map +1 -1
  412. package/build/dist/UI/Components/CustomFields/CustomFieldFormFields.js +169 -0
  413. package/build/dist/UI/Components/CustomFields/CustomFieldFormFields.js.map +1 -0
  414. package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js +212 -0
  415. package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js.map +1 -0
  416. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +64 -49
  417. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
  418. package/build/dist/UI/Components/Forms/ModelForm.js +39 -5
  419. package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
  420. package/build/dist/UI/Components/Forms/Validation.js +1 -1
  421. package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
  422. package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
  423. package/build/dist/UI/Components/ModelTable/AnalyticsModelTable.js.map +1 -1
  424. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +2 -2
  425. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  426. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +13 -1
  427. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
  428. package/build/dist/UI/Components/ModelTable/ModelTable.js +1 -1
  429. package/build/dist/UI/Components/ModelTable/ModelTable.js.map +1 -1
  430. package/build/dist/UI/Utils/SensitiveUrlToken.js +10 -5
  431. package/build/dist/UI/Utils/SensitiveUrlToken.js.map +1 -1
  432. package/build/dist/Utils/API.js +177 -1
  433. package/build/dist/Utils/API.js.map +1 -1
  434. package/build/dist/Utils/Incident/IncidentNoteTemplateVariables.js +155 -0
  435. package/build/dist/Utils/Incident/IncidentNoteTemplateVariables.js.map +1 -0
  436. package/build/dist/Utils/Markdown/MarkdownEscape.js +48 -0
  437. package/build/dist/Utils/Markdown/MarkdownEscape.js.map +1 -1
  438. package/build/dist/Utils/ProbeNetworkFailureUtil.js +460 -0
  439. package/build/dist/Utils/ProbeNetworkFailureUtil.js.map +1 -0
  440. package/package.json +1 -1
@@ -2,21 +2,256 @@ import DatabaseService from "./DatabaseService";
2
2
  import Model from "../../Models/DatabaseModels/StatusPageSubscriberNotificationTemplate";
3
3
  import StatusPageSubscriberNotificationTemplateStatusPage from "../../Models/DatabaseModels/StatusPageSubscriberNotificationTemplateStatusPage";
4
4
  import ObjectID from "../../Types/ObjectID";
5
+ import LIMIT_MAX, { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
6
+ import CreateBy from "../Types/Database/CreateBy";
7
+ import { OnCreate, OnUpdate } from "../Types/Database/Hooks";
8
+ import Query from "../Types/Database/Query";
9
+ import QueryHelper from "../Types/Database/QueryHelper";
10
+ import UpdateBy from "../Types/Database/UpdateBy";
11
+ import SubscriberTemplateIncidentRecordAccess from "../Utils/StatusPage/SubscriberTemplateIncidentRecordAccess";
12
+ import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
5
13
  import StatusPageSubscriberNotificationEventType from "../../Types/StatusPage/StatusPageSubscriberNotificationEventType";
6
14
  import StatusPageSubscriberNotificationMethod from "../../Types/StatusPage/StatusPageSubscriberNotificationMethod";
7
15
  import StatusPageSubscriberNotificationTemplateStatusPageService from "./StatusPageSubscriberNotificationTemplateStatusPageService";
8
16
  import SubscriberNotificationTemplateVariables, {
9
17
  SubscriberNotificationTemplateVariable,
10
18
  } from "../../Types/StatusPage/SubscriberNotificationTemplateVariables";
19
+ import SubscriberNotificationTemplateCompiler, {
20
+ SubscriberNotificationEmailBodyTemplateVariables,
21
+ SubscriberNotificationTextTemplateVariables,
22
+ } from "../../Types/StatusPage/SubscriberNotificationTemplateCompiler";
23
+
24
+ export type {
25
+ SubscriberNotificationEmailBodyTemplateVariables,
26
+ SubscriberNotificationTextTemplateVariables,
27
+ };
11
28
 
12
29
  export class Service extends DatabaseService<Model> {
13
30
  public constructor() {
14
31
  super(Model);
15
32
  }
16
33
 
34
+ /*
35
+ * A template's body and subject may place values from the team's incident
36
+ * records - {{incidentLabels}} and {{customFields.<key>}} - only when
37
+ * whoever writes them may read those records: the status page roles that
38
+ * may write templates may not (SubscriberTemplateIncidentRecordAccess).
39
+ */
40
+ @CaptureSpan()
41
+ protected override async onBeforeCreate(
42
+ createBy: CreateBy<Model>,
43
+ ): Promise<OnCreate<Model>> {
44
+ SubscriberTemplateIncidentRecordAccess.assertCanPlace({
45
+ placeholders:
46
+ SubscriberNotificationTemplateVariables.getIncidentRecordPlaceholders([
47
+ createBy.data.templateBody,
48
+ createBy.data.emailSubject,
49
+ ]),
50
+ props: createBy.props,
51
+ });
52
+
53
+ return { createBy, carryForward: null };
54
+ }
55
+
56
+ /*
57
+ * On an update, only what the write adds is checked: a placeholder the
58
+ * template already holds was allowed when it was written, so someone
59
+ * without incident access can still fix a typo, rename the template or
60
+ * move a placeholder from the subject to the body - but not add one.
61
+ *
62
+ * Pointing a template somewhere else counts as placing everything it
63
+ * holds: changing its channel or event type makes an admin's template
64
+ * send its incident records to wherever the new channel goes, and a
65
+ * status page role can add a Slack, Teams or webhook subscriber of its
66
+ * own. (Linking it to another status page is checked by
67
+ * StatusPageSubscriberNotificationTemplateStatusPageService.)
68
+ */
69
+ @CaptureSpan()
70
+ protected override async onBeforeUpdate(
71
+ updateBy: UpdateBy<Model>,
72
+ ): Promise<OnUpdate<Model>> {
73
+ SubscriberTemplateIncidentRecordAccess.assertCanPlace({
74
+ placeholders: await this.getIncidentRecordPlaceholdersToCheck(updateBy),
75
+ props: updateBy.props,
76
+ });
77
+
78
+ return { updateBy, carryForward: null };
79
+ }
80
+
81
+ /*
82
+ * The incident record placeholders this update must be allowed to place,
83
+ * across every template it would change: those it writes into a body or
84
+ * subject that the template did not hold before, and, when it changes
85
+ * where the template is sent (its channel or event type), every one the
86
+ * template will hold once written. The templates are read as root and
87
+ * limited to the caller's project, like other update hooks: the update's
88
+ * own permission check has not run yet, and only narrows the rows further.
89
+ */
90
+ private async getIncidentRecordPlaceholdersToCheck(
91
+ updateBy: UpdateBy<Model>,
92
+ ): Promise<Array<string>> {
93
+ if (updateBy.props.isRoot || updateBy.props.isMasterAdmin) {
94
+ return [];
95
+ }
96
+
97
+ const data: {
98
+ templateBody?: unknown;
99
+ emailSubject?: unknown;
100
+ eventType?: unknown;
101
+ notificationMethod?: unknown;
102
+ } = updateBy.data as unknown as {
103
+ templateBody?: unknown;
104
+ emailSubject?: unknown;
105
+ eventType?: unknown;
106
+ notificationMethod?: unknown;
107
+ };
108
+ const writesBody: boolean = data.templateBody !== undefined;
109
+ const writesSubject: boolean = data.emailSubject !== undefined;
110
+ const writesTarget: boolean =
111
+ data.eventType !== undefined || data.notificationMethod !== undefined;
112
+
113
+ if (!writesBody && !writesSubject && !writesTarget) {
114
+ return [];
115
+ }
116
+
117
+ // Anything but text places nothing: the column check refuses it anyway.
118
+ const writtenBody: string | null =
119
+ writesBody && typeof data.templateBody === "string"
120
+ ? data.templateBody
121
+ : null;
122
+ const writtenSubject: string | null =
123
+ writesSubject && typeof data.emailSubject === "string"
124
+ ? data.emailSubject
125
+ : null;
126
+
127
+ const written: Array<string> =
128
+ SubscriberNotificationTemplateVariables.getIncidentRecordPlaceholders([
129
+ writtenBody,
130
+ writtenSubject,
131
+ ]);
132
+
133
+ if (written.length === 0 && !writesTarget) {
134
+ return [];
135
+ }
136
+
137
+ const query: Query<Model> = updateBy.props.tenantId
138
+ ? { ...updateBy.query, projectId: updateBy.props.tenantId }
139
+ : updateBy.query;
140
+
141
+ const templates: Array<Model> = await this.findBy({
142
+ query: query,
143
+ select: {
144
+ templateBody: true,
145
+ emailSubject: true,
146
+ eventType: true,
147
+ notificationMethod: true,
148
+ },
149
+ limit: LIMIT_MAX,
150
+ skip: 0,
151
+ props: {
152
+ isRoot: true,
153
+ },
154
+ });
155
+
156
+ const added: Set<string> = new Set<string>();
157
+
158
+ for (const template of templates) {
159
+ const held: Array<string> =
160
+ SubscriberNotificationTemplateVariables.getIncidentRecordPlaceholders([
161
+ template.templateBody,
162
+ template.emailSubject,
163
+ ]);
164
+
165
+ /*
166
+ * Only a real change: the edit form sends the channel and event type
167
+ * back as they were, and a typo fix must still go through.
168
+ */
169
+ const changesTarget: boolean =
170
+ writesTarget &&
171
+ ((data.eventType !== undefined &&
172
+ data.eventType !== template.eventType) ||
173
+ (data.notificationMethod !== undefined &&
174
+ data.notificationMethod !== template.notificationMethod));
175
+
176
+ if (changesTarget) {
177
+ // Everything the template will hold goes to the new target.
178
+ for (const name of SubscriberNotificationTemplateVariables.getIncidentRecordPlaceholders(
179
+ [
180
+ writesBody ? writtenBody : template.templateBody,
181
+ writesSubject ? writtenSubject : template.emailSubject,
182
+ ],
183
+ )) {
184
+ added.add(name);
185
+ }
186
+
187
+ continue;
188
+ }
189
+
190
+ for (const name of written) {
191
+ if (!held.includes(name)) {
192
+ added.add(name);
193
+ }
194
+ }
195
+ }
196
+
197
+ return Array.from(added).sort();
198
+ }
199
+
200
+ /*
201
+ * The incident record placeholders the given templates hold, in the
202
+ * caller's project: what linking them to a status page places there
203
+ * (StatusPageSubscriberNotificationTemplateStatusPageService). Read as
204
+ * root, like the update hook above.
205
+ */
206
+ public async getIncidentRecordPlaceholdersHeld(data: {
207
+ templateIds: Array<ObjectID>;
208
+ projectId?: ObjectID | undefined;
209
+ }): Promise<Array<string>> {
210
+ if (data.templateIds.length === 0) {
211
+ return [];
212
+ }
213
+
214
+ const templates: Array<Model> = await this.findBy({
215
+ query: {
216
+ _id: QueryHelper.any(data.templateIds),
217
+ ...(data.projectId ? { projectId: data.projectId } : {}),
218
+ },
219
+ select: {
220
+ templateBody: true,
221
+ emailSubject: true,
222
+ },
223
+ limit: LIMIT_MAX,
224
+ skip: 0,
225
+ props: {
226
+ isRoot: true,
227
+ },
228
+ });
229
+
230
+ const held: Set<string> = new Set<string>();
231
+
232
+ for (const template of templates) {
233
+ for (const name of SubscriberNotificationTemplateVariables.getIncidentRecordPlaceholders(
234
+ [template.templateBody, template.emailSubject],
235
+ )) {
236
+ held.add(name);
237
+ }
238
+ }
239
+
240
+ return Array.from(held).sort();
241
+ }
242
+
17
243
  /**
18
244
  * Get template for a specific status page, event type, and notification method.
19
245
  * Returns null if no custom template is found (caller should use default template).
246
+ *
247
+ * Workers call this as root, so nothing but the query below keeps one
248
+ * project's page from reading another project's templates. The template
249
+ * query is therefore narrowed to the ids this page links to AND to the
250
+ * page's own project. It used to ask for every template of this event type
251
+ * and channel across all projects, 100 at a time, and look for the linked
252
+ * id in memory: once more than 100 such templates existed on a shared
253
+ * server, the linked one could fall outside those 100 and the page's
254
+ * subscribers silently got the default template instead of the branded one.
20
255
  */
21
256
  public async getTemplateForStatusPage(data: {
22
257
  statusPageId: ObjectID;
@@ -25,7 +260,11 @@ export class Service extends DatabaseService<Model> {
25
260
  }): Promise<Model | null> {
26
261
  const { statusPageId, eventType, notificationMethod } = data;
27
262
 
28
- // First find the template link for this status page
263
+ /*
264
+ * First find the template links for this status page. There is one per
265
+ * linked template, so a page has only a handful, but nothing enforces a
266
+ * cap - so read them all rather than risk the same truncation as above.
267
+ */
29
268
  const templateLinks: Array<StatusPageSubscriberNotificationTemplateStatusPage> =
30
269
  await StatusPageSubscriberNotificationTemplateStatusPageService.findBy({
31
270
  query: {
@@ -33,9 +272,13 @@ export class Service extends DatabaseService<Model> {
33
272
  },
34
273
  select: {
35
274
  statusPageSubscriberNotificationTemplateId: true,
275
+ projectId: true,
276
+ statusPage: {
277
+ projectId: true,
278
+ },
36
279
  },
37
280
  skip: 0,
38
- limit: 100,
281
+ limit: LIMIT_PER_PROJECT,
39
282
  props: {
40
283
  isRoot: true,
41
284
  },
@@ -45,27 +288,52 @@ export class Service extends DatabaseService<Model> {
45
288
  return null;
46
289
  }
47
290
 
48
- // Get the template IDs
291
+ const statusPageProjectId: ObjectID | undefined =
292
+ templateLinks.find(
293
+ (link: StatusPageSubscriberNotificationTemplateStatusPage) => {
294
+ return Boolean(link.statusPage?.projectId);
295
+ },
296
+ )?.statusPage?.projectId || undefined;
297
+
298
+ if (!statusPageProjectId) {
299
+ return null;
300
+ }
301
+
302
+ /*
303
+ * Only links the page's own project made count: a link row stamped with
304
+ * another project cannot pull that project's template onto this page.
305
+ */
49
306
  const templateIds: Array<ObjectID> = templateLinks
307
+ .filter((link: StatusPageSubscriberNotificationTemplateStatusPage) => {
308
+ return link.projectId?.toString() === statusPageProjectId.toString();
309
+ })
50
310
  .map((link: StatusPageSubscriberNotificationTemplateStatusPage) => {
51
311
  return link.statusPageSubscriberNotificationTemplateId;
52
312
  })
53
313
  .filter((id: ObjectID | undefined): id is ObjectID => {
54
- return id !== undefined;
314
+ return id !== undefined && id !== null;
55
315
  });
56
316
 
57
317
  if (templateIds.length === 0) {
58
318
  return null;
59
319
  }
60
320
 
61
- // Find the specific template matching the event type and notification method
321
+ /*
322
+ * Find the linked template for this event type and notification method.
323
+ * Several linked templates can match (nothing stops a page linking two
324
+ * for the same event and channel); the default sort returns the newest
325
+ * first, which is the one this method has always picked.
326
+ */
62
327
  const templates: Array<Model> = await this.findBy({
63
328
  query: {
329
+ _id: QueryHelper.any(templateIds),
330
+ projectId: statusPageProjectId,
64
331
  eventType: eventType,
65
332
  notificationMethod: notificationMethod,
66
333
  },
67
334
  select: {
68
335
  _id: true,
336
+ projectId: true,
69
337
  templateName: true,
70
338
  templateBody: true,
71
339
  emailSubject: true,
@@ -73,24 +341,13 @@ export class Service extends DatabaseService<Model> {
73
341
  notificationMethod: true,
74
342
  },
75
343
  skip: 0,
76
- limit: 100,
344
+ limit: 1,
77
345
  props: {
78
346
  isRoot: true,
79
347
  },
80
348
  });
81
349
 
82
- // Find a template that matches one of the linked template IDs
83
- for (const template of templates) {
84
- if (
85
- templateIds.some((id: ObjectID) => {
86
- return id.toString() === template._id?.toString();
87
- })
88
- ) {
89
- return template;
90
- }
91
- }
92
-
93
- return null;
350
+ return templates[0] || null;
94
351
  }
95
352
 
96
353
  /**
@@ -108,31 +365,36 @@ export class Service extends DatabaseService<Model> {
108
365
  }
109
366
 
110
367
  /**
111
- * Compile a template with the given variables.
112
- * Replaces {{variableName}} with the actual values.
368
+ * Compile a template for a channel that does not render HTML - an email
369
+ * subject, SMS, Slack or Microsoft Teams - replacing {{variableName}} with
370
+ * each value exactly as written.
113
371
  *
114
- * One pass over the template, with a replacer function:
115
- * - A replacement string would give "$&", "$$", "$`" and "$'" special
116
- * meaning, so a note mentioning "$$5" or a resource named "Store $&" was
117
- * rewritten on its way to subscribers.
118
- * - Replacing one variable at a time also expanded placeholders that
119
- * appeared inside an earlier value, so a note containing
120
- * "{{unsubscribeUrl}}" came out as a link.
121
- * A placeholder with no variable is left as written.
372
+ * Never use it for the body of an email template, which is HTML: use
373
+ * compileEmailBodyTemplate. See SubscriberNotificationTemplateCompiler.
122
374
  */
123
375
  public static compileTemplate(
124
376
  template: string,
125
- variables: Record<string, string>,
377
+ variables: SubscriberNotificationTextTemplateVariables,
126
378
  ): string {
127
- return template.replace(
128
- /{{\s*([\w.]+)\s*}}/g,
129
- (placeholder: string, name: string): string => {
130
- if (!Object.prototype.hasOwnProperty.call(variables, name)) {
131
- return placeholder;
132
- }
379
+ return SubscriberNotificationTemplateCompiler.compileTemplate(
380
+ template,
381
+ variables,
382
+ );
383
+ }
133
384
 
134
- return variables[name] || "";
135
- },
385
+ /**
386
+ * Compile the body of an EMAIL template, which is sent as HTML. Plain
387
+ * values are HTML-escaped; only a SafeHtml value (rendered Markdown, the
388
+ * date helper's HTML, the escaped resource list) is inserted as it is. See
389
+ * SubscriberNotificationTemplateCompiler.
390
+ */
391
+ public static compileEmailBodyTemplate(
392
+ template: string,
393
+ variables: SubscriberNotificationEmailBodyTemplateVariables,
394
+ ): string {
395
+ return SubscriberNotificationTemplateCompiler.compileEmailBodyTemplate(
396
+ template,
397
+ variables,
136
398
  );
137
399
  }
138
400
  }
@@ -1,10 +1,139 @@
1
1
  import DatabaseService from "./DatabaseService";
2
+ import StatusPageSubscriberNotificationTemplateService from "./StatusPageSubscriberNotificationTemplateService";
2
3
  import Model from "../../Models/DatabaseModels/StatusPageSubscriberNotificationTemplateStatusPage";
4
+ import LIMIT_MAX from "../../Types/Database/LimitMax";
5
+ import ObjectID from "../../Types/ObjectID";
6
+ import CreateBy from "../Types/Database/CreateBy";
7
+ import { OnCreate, OnUpdate } from "../Types/Database/Hooks";
8
+ import Query from "../Types/Database/Query";
9
+ import UpdateBy from "../Types/Database/UpdateBy";
10
+ import SubscriberTemplateIncidentRecordAccess from "../Utils/StatusPage/SubscriberTemplateIncidentRecordAccess";
11
+ import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
3
12
 
13
+ /*
14
+ * Linking a custom subscriber notification template to a status page sends
15
+ * that page's subscribers whatever the template places. A template may place
16
+ * values from the team's incident records ({{incidentLabels}},
17
+ * {{customFields.<key>}}), which only someone who may read them may put in a
18
+ * template (SubscriberTemplateIncidentRecordAccess) - and the status page
19
+ * roles that may link templates may also add a Slack, Teams or webhook
20
+ * subscriber of their own to a page. So linking such a template, or moving a
21
+ * link to another template or page, is checked as placing everything the
22
+ * template holds. Root and master admin writes are not checked.
23
+ */
4
24
  export class Service extends DatabaseService<Model> {
5
25
  public constructor() {
6
26
  super(Model);
7
27
  }
28
+
29
+ @CaptureSpan()
30
+ protected override async onBeforeCreate(
31
+ createBy: CreateBy<Model>,
32
+ ): Promise<OnCreate<Model>> {
33
+ if (!createBy.props.isRoot && !createBy.props.isMasterAdmin) {
34
+ const templateId: ObjectID | undefined =
35
+ createBy.data.statusPageSubscriberNotificationTemplateId ||
36
+ createBy.data.statusPageSubscriberNotificationTemplate?.id ||
37
+ undefined;
38
+
39
+ await this.assertCanLink({
40
+ templateIds: templateId ? [templateId] : [],
41
+ createBy: createBy,
42
+ });
43
+ }
44
+
45
+ return { createBy, carryForward: null };
46
+ }
47
+
48
+ @CaptureSpan()
49
+ protected override async onBeforeUpdate(
50
+ updateBy: UpdateBy<Model>,
51
+ ): Promise<OnUpdate<Model>> {
52
+ if (updateBy.props.isRoot || updateBy.props.isMasterAdmin) {
53
+ return { updateBy, carryForward: null };
54
+ }
55
+
56
+ const data: {
57
+ statusPageSubscriberNotificationTemplateId?: unknown;
58
+ statusPageId?: unknown;
59
+ } = updateBy.data as unknown as {
60
+ statusPageSubscriberNotificationTemplateId?: unknown;
61
+ statusPageId?: unknown;
62
+ };
63
+
64
+ const writesTemplate: boolean =
65
+ data.statusPageSubscriberNotificationTemplateId !== undefined &&
66
+ data.statusPageSubscriberNotificationTemplateId !== null;
67
+ const writesStatusPage: boolean = data.statusPageId !== undefined;
68
+
69
+ if (!writesTemplate && !writesStatusPage) {
70
+ return { updateBy, carryForward: null };
71
+ }
72
+
73
+ let templateIds: Array<ObjectID> = [];
74
+
75
+ if (writesTemplate) {
76
+ templateIds = [
77
+ new ObjectID(String(data.statusPageSubscriberNotificationTemplateId)),
78
+ ];
79
+ } else {
80
+ // Moved to another page: the templates the links already point at.
81
+ const query: Query<Model> = updateBy.props.tenantId
82
+ ? { ...updateBy.query, projectId: updateBy.props.tenantId }
83
+ : updateBy.query;
84
+
85
+ const links: Array<Model> = await this.findBy({
86
+ query: query,
87
+ select: {
88
+ statusPageSubscriberNotificationTemplateId: true,
89
+ },
90
+ limit: LIMIT_MAX,
91
+ skip: 0,
92
+ props: {
93
+ isRoot: true,
94
+ },
95
+ });
96
+
97
+ templateIds = links
98
+ .map((link: Model): ObjectID | undefined => {
99
+ return link.statusPageSubscriberNotificationTemplateId;
100
+ })
101
+ .filter((id: ObjectID | undefined): id is ObjectID => {
102
+ return Boolean(id);
103
+ });
104
+ }
105
+
106
+ SubscriberTemplateIncidentRecordAccess.assertCanPlace({
107
+ placeholders:
108
+ await StatusPageSubscriberNotificationTemplateService.getIncidentRecordPlaceholdersHeld(
109
+ {
110
+ templateIds: templateIds,
111
+ projectId: updateBy.props.tenantId as ObjectID | undefined,
112
+ },
113
+ ),
114
+ props: updateBy.props,
115
+ });
116
+
117
+ return { updateBy, carryForward: null };
118
+ }
119
+
120
+ private async assertCanLink(data: {
121
+ templateIds: Array<ObjectID>;
122
+ createBy: CreateBy<Model>;
123
+ }): Promise<void> {
124
+ SubscriberTemplateIncidentRecordAccess.assertCanPlace({
125
+ placeholders:
126
+ await StatusPageSubscriberNotificationTemplateService.getIncidentRecordPlaceholdersHeld(
127
+ {
128
+ templateIds: data.templateIds,
129
+ projectId:
130
+ (data.createBy.props.tenantId as ObjectID | undefined) ||
131
+ data.createBy.data.projectId,
132
+ },
133
+ ),
134
+ props: data.createBy.props,
135
+ });
136
+ }
8
137
  }
9
138
 
10
139
  export default new Service();