@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
@@ -1,6 +1,7 @@
1
1
  import { Renderer, marked } from "marked";
2
2
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
3
3
  import markdownSlugify from "./MarkdownSlugify";
4
+ import SafeHtml from "../../Types/SafeHtml";
4
5
 
5
6
  export type MarkdownRenderer = Renderer;
6
7
 
@@ -50,6 +51,26 @@ const HTML_ENTITIES: Record<string, string> = {
50
51
  nbsp: " ",
51
52
  };
52
53
 
54
+ // The named references getEmailUrl decodes in a link destination.
55
+ const URL_NAMED_CHARACTER_REFERENCES: Record<string, string> = {
56
+ amp: "&",
57
+ lt: "<",
58
+ gt: ">",
59
+ quot: '"',
60
+ apos: "'",
61
+ };
62
+
63
+ /*
64
+ * The schemes an email may link to, and load an image from. A destination
65
+ * with no scheme is also allowed (see Markdown.getEmailUrl).
66
+ */
67
+ export const EMAIL_LINK_SCHEMES: ReadonlyArray<string> = [
68
+ "http",
69
+ "https",
70
+ "mailto",
71
+ ];
72
+ export const EMAIL_IMAGE_SCHEMES: ReadonlyArray<string> = ["http", "https"];
73
+
53
74
  type HoldFunction = (value: string) => string;
54
75
 
55
76
  interface BacktickRuns {
@@ -603,12 +624,93 @@ export default class Markdown {
603
624
  * a second implementation is a second thing to get wrong.
604
625
  */
605
626
  public static escapeHtml(text: string): string {
606
- return text
607
- .replace(/&/g, "&amp;")
608
- .replace(/</g, "&lt;")
609
- .replace(/>/g, "&gt;")
610
- .replace(/"/g, "&quot;")
611
- .replace(/'/g, "&#39;");
627
+ return SafeHtml.escape(text);
628
+ }
629
+
630
+ /**
631
+ * The URL a link or image in an EMAIL may point at, ready to go between
632
+ * the double quotes of an href or src attribute - or null when it may not
633
+ * be emitted at all.
634
+ *
635
+ * An email's Markdown comes from people outside the recipient's control: a
636
+ * public note, an incident description, a root cause built from telemetry.
637
+ * marked emits any destination it is given, so `[Details](javascript:...)`
638
+ * or `(data:text/html;base64,...)` became a live link in a status update
639
+ * the subscriber trusts. Only the schemes listed are kept; a destination
640
+ * with no scheme (a relative link or "#anchor") is kept too, as it was
641
+ * before, and cannot run anything.
642
+ *
643
+ * The scheme is judged on exactly the URL the recipient's mail client will
644
+ * see:
645
+ * - The character references marked leaves in a destination
646
+ * ("&#x6A;avascript:") are decoded first, and the result is percent-
647
+ * encoded the way marked does it (encodeURI), which also encodes every
648
+ * space and control character a browser would strip out of a scheme.
649
+ * - The URL is then HTML-escaped into the attribute, so the client's own
650
+ * decoding of the attribute gives back exactly the string that was
651
+ * checked. A character reference this does not decode ("&colon;")
652
+ * therefore reaches the client as literal text, never as a ":".
653
+ */
654
+ public static getEmailUrl(
655
+ href: string | null | undefined,
656
+ allowedSchemes: ReadonlyArray<string>,
657
+ ): string | null {
658
+ if (!href) {
659
+ return null;
660
+ }
661
+
662
+ let url: string;
663
+
664
+ try {
665
+ url = encodeURI(Markdown.decodeUrlCharacterReferences(href)).replace(
666
+ /%25/g,
667
+ "%",
668
+ );
669
+ } catch {
670
+ // A lone surrogate, which encodeURI refuses: no usable URL.
671
+ return null;
672
+ }
673
+
674
+ const scheme: RegExpMatchArray | null = url.match(
675
+ /^([a-zA-Z][a-zA-Z0-9+.-]*):/,
676
+ );
677
+
678
+ if (scheme && !allowedSchemes.includes(scheme[1]!.toLowerCase())) {
679
+ return null;
680
+ }
681
+
682
+ return Markdown.escapeHtml(url);
683
+ }
684
+
685
+ /*
686
+ * Numeric character references, and the named ones for the characters
687
+ * escapeHtml writes. Anything else is left as written, and getEmailUrl's
688
+ * escaping makes it inert.
689
+ */
690
+ private static decodeUrlCharacterReferences(text: string): string {
691
+ return text.replace(
692
+ /&(#[xX][0-9a-fA-F]+|#[0-9]+|amp|lt|gt|quot|apos);/g,
693
+ (match: string, reference: string): string => {
694
+ if (reference.startsWith("#")) {
695
+ const codePoint: number =
696
+ reference[1] === "x" || reference[1] === "X"
697
+ ? parseInt(reference.slice(2), 16)
698
+ : parseInt(reference.slice(1), 10);
699
+
700
+ if (
701
+ !Number.isFinite(codePoint) ||
702
+ codePoint <= 0 ||
703
+ codePoint > 0x10ffff
704
+ ) {
705
+ return match;
706
+ }
707
+
708
+ return String.fromCodePoint(codePoint);
709
+ }
710
+
711
+ return URL_NAMED_CHARACTER_REFERENCES[reference] ?? match;
712
+ },
713
+ );
612
714
  }
613
715
 
614
716
  /*
@@ -743,6 +845,51 @@ export default class Markdown {
743
845
  );
744
846
  };
745
847
 
848
+ /*
849
+ * Links and images keep only http, https and mailto destinations (http
850
+ * and https for an image), plus relative ones: see getEmailUrl. Any
851
+ * other link renders as its text alone, and any other image as its alt
852
+ * text, so the words the author wrote still read in place.
853
+ *
854
+ * The markup is marked's own. `text` is the link's rendered inline
855
+ * content, and `title` and an image's alt text arrive already escaped by
856
+ * marked's tokenizer, so escaping them again would show "&amp;".
857
+ */
858
+ renderer.link = function (
859
+ href: string,
860
+ title: string | null | undefined,
861
+ text: string,
862
+ ): string {
863
+ const url: string | null = Markdown.getEmailUrl(href, EMAIL_LINK_SCHEMES);
864
+
865
+ if (url === null) {
866
+ return text;
867
+ }
868
+
869
+ const titleAttribute: string = title ? ` title="${title}"` : "";
870
+
871
+ return `<a href="${url}"${titleAttribute}>${text}</a>`;
872
+ };
873
+
874
+ renderer.image = function (
875
+ href: string,
876
+ title: string | null,
877
+ text: string,
878
+ ): string {
879
+ const url: string | null = Markdown.getEmailUrl(
880
+ href,
881
+ EMAIL_IMAGE_SCHEMES,
882
+ );
883
+
884
+ if (url === null) {
885
+ return text;
886
+ }
887
+
888
+ const titleAttribute: string = title ? ` title="${title}"` : "";
889
+
890
+ return `<img src="${url}" alt="${text}"${titleAttribute}>`;
891
+ };
892
+
746
893
  this.emailRenderer = renderer;
747
894
 
748
895
  return renderer;
@@ -6,9 +6,18 @@ import ComponentID from "../../../../Types/Workflow/ComponentID";
6
6
  import Components from "../../../../Types/Workflow/Components/Email";
7
7
  import nodemailer, { Transporter } from "nodemailer";
8
8
  import SMTPTransport from "nodemailer/lib/smtp-transport";
9
+ import DataSourceEgressGuard, {
10
+ ResolvedAddress,
11
+ } from "../../../Utils/DataSource/EgressGuard";
12
+ import PinnedSmtpSocket, {
13
+ SmtpSocketCallback,
14
+ } from "../../../Utils/Mail/PinnedSmtpSocket";
9
15
  import CaptureSpan from "../../../Utils/Telemetry/CaptureSpan";
10
16
 
11
17
  export default class Email extends ComponentCode {
18
+ // nodemailer's own default connection timeout.
19
+ private static readonly CONNECTION_TIMEOUT_IN_MS: number = 2 * 60 * 1000;
20
+
12
21
  public constructor() {
13
22
  super();
14
23
 
@@ -123,10 +132,60 @@ export default class Email extends ComponentCode {
123
132
  };
124
133
  }
125
134
 
135
+ const smtpHost: string = args["smtp-host"].toString().trim();
136
+ // "[fd00::25]" is how an IPv6 literal is usually written next to a port.
137
+ const bareSmtpHost: string = smtpHost.replace(/^\[|\]$/g, "");
138
+ const smtpPort: number = args["smtp-port"] as number;
139
+
126
140
  try {
141
+ /*
142
+ * The SMTP host is free text typed into the workflow node by any
143
+ * project member who can edit workflows, and nodemailer's failures
144
+ * ("Invalid greeting. response=<banner>", refused vs timed out) come
145
+ * back through the Error port into the workflow log. Unchecked, this
146
+ * step is a port scanner with banner disclosure for whatever network
147
+ * the workflow worker runs in.
148
+ *
149
+ * Same guard and label as a project's own SMTP server in MailService,
150
+ * so the same policy: loopback, link-local and cloud metadata are
151
+ * refused everywhere; private ranges are refused on SaaS and allowed on
152
+ * self-hosted installs unless DATA_SOURCE_BLOCK_PRIVATE_ADDRESSES=true.
153
+ * A refusal is thrown inside this try, so it takes the Error port like
154
+ * any other failure to send, before a socket is opened.
155
+ */
156
+ const validatedAddresses: Array<ResolvedAddress> =
157
+ await DataSourceEgressGuard.assertHostnameAllowed(bareSmtpHost, {
158
+ targetLabel: "SMTP server",
159
+ });
160
+
127
161
  const smtpTransport: SMTPTransport.Options = {
128
- host: args["smtp-host"]?.toString(),
129
- port: args["smtp-port"] as number,
162
+ /*
163
+ * nodemailer still needs the name the user typed: it is what TLS
164
+ * sends as SNI and checks the certificate against, on implicit TLS
165
+ * and on the STARTTLS upgrade alike. It is never dialed; see
166
+ * getSocket.
167
+ */
168
+ host: bareSmtpHost,
169
+ port: smtpPort,
170
+ /*
171
+ * Connect here, to the addresses just validated, and hand nodemailer
172
+ * the open socket. Given the name instead, nodemailer resolves it
173
+ * again on its own (dns.resolve4/6, then a process-wide five-minute
174
+ * cache), so a DNS answer that changed after the check would choose
175
+ * the address actually connected to.
176
+ */
177
+ getSocket: (
178
+ _options: SMTPTransport.Options,
179
+ callback: SmtpSocketCallback,
180
+ ): void => {
181
+ PinnedSmtpSocket.connect({
182
+ host: bareSmtpHost,
183
+ port: smtpPort,
184
+ addresses: validatedAddresses,
185
+ timeoutInMs: Email.CONNECTION_TIMEOUT_IN_MS,
186
+ onDone: callback,
187
+ });
188
+ },
130
189
  };
131
190
 
132
191
  if (
@@ -0,0 +1,378 @@
1
+ import BaseModel from "../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
2
+ import TableView from "../../../Models/DatabaseModels/TableView";
3
+ import {
4
+ CustomFieldSavedViewState,
5
+ RenamedCustomFieldSavedViewState,
6
+ renameCustomFieldInSavedView,
7
+ } from "../../../Types/CustomField/CustomFieldSavedViews";
8
+ import LIMIT_MAX from "../../../Types/Database/LimitMax";
9
+ import { JSONObject } from "../../../Types/JSON";
10
+ import ObjectID from "../../../Types/ObjectID";
11
+ import DatabaseService from "../../Services/DatabaseService";
12
+ import TableViewService from "../../Services/TableViewService";
13
+ import QueryHelper from "../../Types/Database/QueryHelper";
14
+ import logger, { LogAttributes } from "../Logger";
15
+ import { ColumnMetadata } from "typeorm/metadata/ColumnMetadata";
16
+ import { EntityManager, EntityMetadata, Repository } from "typeorm";
17
+
18
+ /*
19
+ * RENAMING A CUSTOM FIELD.
20
+ *
21
+ * Custom field values live in each record's `customFields` jsonb bag, keyed
22
+ * by the field's display name, so renaming "Impact" to "Business Impact"
23
+ * orphans every stored value unless the key moves with it. This moves it:
24
+ * one UPDATE per table, for the whole project, that takes the value out from
25
+ * under the old key and puts it back under the new one - and does the same
26
+ * to the saved table views that name the field.
27
+ *
28
+ * THE NEW NAME STARTS CLEAN. Deleting a field leaves its values in the
29
+ * records (see the custom fields docs), so a record can hold a value under
30
+ * the new name that belongs to no field: a deleted field's answer. The
31
+ * renamed field must not pick those up - they were never its values, and an
32
+ * incident field marked for subscriber notifications would send them out -
33
+ * so the same UPDATE removes the new name from every record of the project
34
+ * that holds nothing under the old one. After a rename, the values under the
35
+ * new name are exactly the ones that were under the old.
36
+ *
37
+ * WHY NOT DatabaseService.updateBy. It fires the model's "on update" workflow
38
+ * trigger for every changed row, even with ignoreHooks (see _updateBy), and
39
+ * customers' own workflows listen on incident custom fields. A rename is a
40
+ * change of label, not of any incident's answers; it must not start a
41
+ * workflow run per incident. The raw statements here fire no workflow, no
42
+ * realtime event and no audit entry, bump no `version`, and leave `updatedAt`
43
+ * alone, like the other derived-data writes (CustomFieldMappingService).
44
+ *
45
+ * The table and column names come from the entity metadata, never from the
46
+ * caller, and every value is a bound parameter, so the SQL is not an
47
+ * injection surface.
48
+ */
49
+
50
+ export interface MoveCustomFieldValueKeyInput {
51
+ // The service whose table holds values under the field's name.
52
+ service: DatabaseService<any>;
53
+ projectId: ObjectID;
54
+ oldName: string;
55
+ newName: string;
56
+ // The transaction to run the statement in; on its own when not given.
57
+ manager?: EntityManager | undefined;
58
+ }
59
+
60
+ type GetColumnNameFunction = (
61
+ metadata: EntityMetadata,
62
+ propertyName: string,
63
+ ) => string;
64
+
65
+ const getColumnName: GetColumnNameFunction = (
66
+ metadata: EntityMetadata,
67
+ propertyName: string,
68
+ ): string => {
69
+ const column: ColumnMetadata | undefined =
70
+ metadata.findColumnWithPropertyName(propertyName);
71
+
72
+ if (!column) {
73
+ throw new Error(
74
+ `Cannot rename a custom field on ${metadata.tableName}: it has no "${propertyName}" column.`,
75
+ );
76
+ }
77
+
78
+ return column.databaseName;
79
+ };
80
+
81
+ export interface MoveCustomFieldValueKeyResult {
82
+ // Records whose value moved from the old name to the new one.
83
+ moved: number;
84
+ // Records that held a value under the new name only, now removed.
85
+ cleared: number;
86
+ }
87
+
88
+ /**
89
+ * Move every value stored under `oldName` to `newName`, on every record of
90
+ * the project in the service's table, and clear what records that hold
91
+ * nothing under `oldName` have under `newName`.
92
+ *
93
+ * Only records whose bag is a JSON object holding one of the two names are
94
+ * written. A value the record already held under `newName` is replaced by
95
+ * the one under `oldName`, or removed when there is none: the renamed
96
+ * field's answer is the only one that counts. One statement, so no reader
97
+ * sees the values half moved.
98
+ */
99
+ export const moveCustomFieldValueKey: (
100
+ input: MoveCustomFieldValueKeyInput,
101
+ ) => Promise<MoveCustomFieldValueKeyResult> = async (
102
+ input: MoveCustomFieldValueKeyInput,
103
+ ): Promise<MoveCustomFieldValueKeyResult> => {
104
+ if (!input.oldName || !input.newName || input.oldName === input.newName) {
105
+ return { moved: 0, cleared: 0 };
106
+ }
107
+
108
+ const repository: Repository<BaseModel> = input.service.getRepository();
109
+ const metadata: EntityMetadata = repository.metadata;
110
+ const customFieldsColumn: string = getColumnName(metadata, "customFields");
111
+ const projectIdColumn: string = getColumnName(metadata, "projectId");
112
+
113
+ /*
114
+ * In a CTE so the statement answers with the rows it wrote: TypeORM hands
115
+ * back a top-level UPDATE as [rows, rowCount] whatever happened. RETURNING
116
+ * reads the row as written, so a record holds the new name afterwards
117
+ * exactly when its value was moved there.
118
+ *
119
+ * jsonb_typeof guards the operators: on a jsonb array `-` and `?` act on
120
+ * the array's string elements, and a bag that is not an object is not a
121
+ * bag of named values to rename.
122
+ */
123
+ const sql: string = `WITH "renamed" AS (
124
+ UPDATE "${metadata.tableName}"
125
+ SET "${customFieldsColumn}" = CASE
126
+ WHEN jsonb_exists("${customFieldsColumn}", $2::text)
127
+ THEN ("${customFieldsColumn}" - $2::text) || jsonb_build_object($3::text, "${customFieldsColumn}" -> $2::text)
128
+ ELSE "${customFieldsColumn}" - $3::text
129
+ END
130
+ WHERE "${projectIdColumn}" = $1
131
+ AND jsonb_typeof("${customFieldsColumn}") = 'object'
132
+ AND (
133
+ jsonb_exists("${customFieldsColumn}", $2::text)
134
+ OR jsonb_exists("${customFieldsColumn}", $3::text)
135
+ )
136
+ RETURNING jsonb_exists("${customFieldsColumn}", $3::text) AS "moved"
137
+ ) SELECT
138
+ (COUNT(*) FILTER (WHERE "moved"))::int AS "moved",
139
+ (COUNT(*) FILTER (WHERE NOT "moved"))::int AS "cleared"
140
+ FROM "renamed"`;
141
+
142
+ const manager: EntityManager = input.manager || repository.manager;
143
+
144
+ const result: unknown = await manager.query(sql, [
145
+ input.projectId.toString(),
146
+ input.oldName,
147
+ input.newName,
148
+ ]);
149
+
150
+ const row: JSONObject | undefined = Array.isArray(result)
151
+ ? (result[0] as JSONObject | undefined)
152
+ : undefined;
153
+
154
+ return {
155
+ moved: Number(row?.["moved"]) || 0,
156
+ cleared: Number(row?.["cleared"]) || 0,
157
+ };
158
+ };
159
+
160
+ export interface RenameCustomFieldInTableViewsInput {
161
+ projectId: ObjectID;
162
+ // The saved views to look at, by TableView.tableId.
163
+ tableIds: Array<string>;
164
+ oldName: string;
165
+ newName: string;
166
+ }
167
+
168
+ /**
169
+ * Rewrite the saved table views of the project that name the field, so a
170
+ * view keeps its column and its filter chip after the rename. Returns how
171
+ * many views it rewrote.
172
+ *
173
+ * Each view is written with a compare-and-set on the columns it was read
174
+ * with, so a view someone saves at the same moment is left as they saved it
175
+ * rather than half overwritten.
176
+ */
177
+ export const renameCustomFieldInTableViews: (
178
+ input: RenameCustomFieldInTableViewsInput,
179
+ ) => Promise<number> = async (
180
+ input: RenameCustomFieldInTableViewsInput,
181
+ ): Promise<number> => {
182
+ if (
183
+ !input.oldName ||
184
+ !input.newName ||
185
+ input.oldName === input.newName ||
186
+ input.tableIds.length === 0
187
+ ) {
188
+ return 0;
189
+ }
190
+
191
+ const views: Array<TableView> = await TableViewService.findBy({
192
+ query: {
193
+ projectId: input.projectId,
194
+ tableId: QueryHelper.any(input.tableIds),
195
+ },
196
+ select: {
197
+ _id: true,
198
+ query: true,
199
+ facets: true,
200
+ columns: true,
201
+ },
202
+ limit: LIMIT_MAX,
203
+ skip: 0,
204
+ props: {
205
+ isRoot: true,
206
+ },
207
+ });
208
+
209
+ let renamedCount: number = 0;
210
+
211
+ for (const view of views) {
212
+ const current: CustomFieldSavedViewState = {
213
+ query: (view.query as unknown as JSONObject | undefined) ?? null,
214
+ facets: view.facets ?? null,
215
+ columns: view.columns ?? null,
216
+ };
217
+
218
+ const renamed: RenamedCustomFieldSavedViewState =
219
+ renameCustomFieldInSavedView({
220
+ view: current,
221
+ oldName: input.oldName,
222
+ newName: input.newName,
223
+ });
224
+
225
+ if (!renamed.hasChanged || !view.id) {
226
+ continue;
227
+ }
228
+
229
+ const data: Record<string, unknown> = {};
230
+ const expectedData: Record<string, unknown> = {};
231
+
232
+ for (const key of Object.keys(renamed.changes) as Array<
233
+ keyof CustomFieldSavedViewState
234
+ >) {
235
+ data[key] = renamed.changes[key];
236
+ expectedData[key] = current[key];
237
+ }
238
+
239
+ await TableViewService.updateColumnsByIdWithoutHooks({
240
+ id: view.id,
241
+ data: data as never,
242
+ expectedData: expectedData as never,
243
+ skipUpdateDateColumn: true,
244
+ });
245
+
246
+ renamedCount++;
247
+ }
248
+
249
+ return renamedCount;
250
+ };
251
+
252
+ export interface RenameCustomFieldInput {
253
+ projectId: ObjectID;
254
+ oldName: string;
255
+ newName: string;
256
+ // Every service whose table stores values under the field's name.
257
+ valueServices: Array<DatabaseService<any>>;
258
+ // The saved views of tables that show the field.
259
+ tableViewIds: Array<string>;
260
+ // How the field is named in logs, e.g. "incident custom field".
261
+ definitionName: string;
262
+ /*
263
+ * Called when the values could not be moved, and are all still under the
264
+ * old name, before the failure is thrown: the caller gives the field its
265
+ * old name back, so the values stay in view (see renameCustomField).
266
+ */
267
+ onValuesNotMoved?: (() => Promise<void>) | undefined;
268
+ }
269
+
270
+ export interface RenameCustomFieldResult {
271
+ movedRecordsByTable: Record<string, number>;
272
+ // Records whose stale value under the new name was removed, by table.
273
+ clearedRecordsByTable: Record<string, number>;
274
+ renamedTableViews: number;
275
+ }
276
+
277
+ /**
278
+ * Everything a rename has to move: the stored values in each table, then the
279
+ * saved views.
280
+ *
281
+ * The values move in one transaction across the tables, so either every
282
+ * table holds them under the new name or none does. That matters because
283
+ * the move also clears stale values under the new name: had one table moved
284
+ * and another not, renaming the field back would clear the values the
285
+ * second table still held under the old name. When the transaction fails,
286
+ * onValuesNotMoved runs - the caller puts the field's old name back, so the
287
+ * field and its values agree again - and the failure is thrown. A saved view
288
+ * that could not be rewritten only loses the field's column or filter, and
289
+ * throws too.
290
+ */
291
+ export const renameCustomField: (
292
+ input: RenameCustomFieldInput,
293
+ ) => Promise<RenameCustomFieldResult> = async (
294
+ input: RenameCustomFieldInput,
295
+ ): Promise<RenameCustomFieldResult> => {
296
+ const result: RenameCustomFieldResult = {
297
+ movedRecordsByTable: {},
298
+ clearedRecordsByTable: {},
299
+ renamedTableViews: 0,
300
+ };
301
+
302
+ if (!input.oldName || !input.newName || input.oldName === input.newName) {
303
+ return result;
304
+ }
305
+
306
+ const firstService: DatabaseService<any> | undefined = input.valueServices[0];
307
+
308
+ if (firstService) {
309
+ try {
310
+ await firstService
311
+ .getRepository()
312
+ .manager.transaction(async (manager: EntityManager): Promise<void> => {
313
+ for (const service of input.valueServices) {
314
+ const tableName: string =
315
+ service.getModel().tableName ||
316
+ service.getRepository().metadata.name;
317
+
318
+ const moved: MoveCustomFieldValueKeyResult =
319
+ await moveCustomFieldValueKey({
320
+ service: service,
321
+ projectId: input.projectId,
322
+ oldName: input.oldName,
323
+ newName: input.newName,
324
+ manager: manager,
325
+ });
326
+
327
+ result.movedRecordsByTable[tableName] = moved.moved;
328
+ result.clearedRecordsByTable[tableName] = moved.cleared;
329
+ }
330
+ });
331
+ } catch (err) {
332
+ logger.error(
333
+ `Could not move ${input.definitionName} values from ${JSON.stringify(
334
+ input.oldName,
335
+ )} to ${JSON.stringify(input.newName)}; none were moved.`,
336
+ {
337
+ projectId: input.projectId.toString(),
338
+ } as LogAttributes,
339
+ );
340
+
341
+ if (input.onValuesNotMoved) {
342
+ try {
343
+ await input.onValuesNotMoved();
344
+ } catch (restoreError) {
345
+ logger.error(restoreError, {
346
+ projectId: input.projectId.toString(),
347
+ } as LogAttributes);
348
+ }
349
+ }
350
+
351
+ throw err;
352
+ }
353
+ }
354
+
355
+ result.renamedTableViews = await renameCustomFieldInTableViews({
356
+ projectId: input.projectId,
357
+ tableIds: input.tableViewIds,
358
+ oldName: input.oldName,
359
+ newName: input.newName,
360
+ });
361
+
362
+ logger.info(
363
+ `Renamed ${input.definitionName} values from ${JSON.stringify(
364
+ input.oldName,
365
+ )} to ${JSON.stringify(input.newName)}: ${JSON.stringify(
366
+ result.movedRecordsByTable,
367
+ )} records moved, ${JSON.stringify(
368
+ result.clearedRecordsByTable,
369
+ )} stale values under the new name cleared, ${
370
+ result.renamedTableViews
371
+ } saved views.`,
372
+ {
373
+ projectId: input.projectId.toString(),
374
+ } as LogAttributes,
375
+ );
376
+
377
+ return result;
378
+ };