@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
@@ -74,6 +74,17 @@ import TooManyRequestsException from "../../Types/Exception/TooManyRequestsExcep
74
74
  * DatabaseNotConnectedException when Redis is down. Logging in is already
75
75
  * impossible in that state; the difference this makes is a 503 that says to
76
76
  * come back shortly instead of a 500.
77
+ *
78
+ * ONE ROUTE HERE SENDS MAIL RATHER THAN CHECKING A CREDENTIAL
79
+ *
80
+ * POST /resend-verification-email (Authentication.ts) is limited by this
81
+ * middleware too, on a bucket of its own. It is not a guessing oracle against
82
+ * a stored credential -- the credential it takes is server-minted and signed,
83
+ * or a random UUID from an emailed link -- but every request that gets past its
84
+ * checks makes the server send an email, and an anonymous route that sends
85
+ * mail needs a ceiling in front of its database reads and its caps. It shares
86
+ * the fail-closed choice for the same reason: its own cooldown fence lives in
87
+ * Redis, so with Redis down it could not bound anything either.
77
88
  */
78
89
 
79
90
  /*
@@ -144,6 +155,15 @@ export enum IdentityRateLimitBucket {
144
155
 
145
156
  // Discoverable passkey requests have no account identifier until verified.
146
157
  Passkey = "passkey",
158
+
159
+ /*
160
+ * POST /resend-verification-email -- an unverified account asking for its
161
+ * verification link again. Mail, not guessing: see
162
+ * VERIFICATION_EMAIL_RESEND_BUCKET. Its own counter so that a user mashing
163
+ * "resend" cannot spend the sign-in attempts they are about to need, and so
164
+ * that sign-in noise cannot refuse the resend.
165
+ */
166
+ VerificationEmailResend = "verification-email-resend",
147
167
  }
148
168
 
149
169
  export enum IdentityRateLimitScope {
@@ -413,6 +433,55 @@ const PASSKEY_BUCKET: BucketConfig = {
413
433
  perIpLimit: PASSKEY_RATE_LIMIT,
414
434
  };
415
435
 
436
+ /*
437
+ * Verification email resend budget.
438
+ *
439
+ * Shaped like the passkey bucket, and for the same reason: the request carries
440
+ * no email address (it carries a signed resend token or a verification link
441
+ * token instead), so resolveAccountKey answers "none" and the account counter
442
+ * is keyed on the client address alone. Both counters are therefore the same
443
+ * per-address budget and are given the same number; a caller who adds an
444
+ * `email` field to rotate the account key still meets the address ceiling.
445
+ *
446
+ * This is NOT what bounds mail to any one inbox. The route's own caps do that
447
+ * per account -- a cooldown between sends, a per-hour cap, and a small fixed
448
+ * number of sends per credential (VerificationEmailResendPolicy) -- and those
449
+ * hold however many addresses a caller has. What this bounds is how much work
450
+ * one address can make the route do before those caps are even consulted:
451
+ * token verification, a verification-token lookup, a user lookup and a Redis
452
+ * read per request.
453
+ *
454
+ * Twenty a quarter hour is far past an honest user, who presses the button a
455
+ * handful of times at most because the page itself counts the cooldown down,
456
+ * and still generous to an office behind one NAT where several people are
457
+ * finishing signup at once.
458
+ */
459
+ const VERIFICATION_EMAIL_RESEND_RATE_LIMIT: number = parsePositiveIntFromEnv(
460
+ "IDENTITY_VERIFICATION_EMAIL_RESEND_RATE_LIMIT_PER_IP_PER_WINDOW",
461
+ 20,
462
+ );
463
+ const VERIFICATION_EMAIL_RESEND_BUCKET: BucketConfig = {
464
+ windowSeconds: parsePositiveIntFromEnv(
465
+ "IDENTITY_VERIFICATION_EMAIL_RESEND_RATE_LIMIT_WINDOW_SECONDS",
466
+ 15 * 60,
467
+ ),
468
+ perAccountLimit: VERIFICATION_EMAIL_RESEND_RATE_LIMIT,
469
+ perIpLimit: VERIFICATION_EMAIL_RESEND_RATE_LIMIT,
470
+ };
471
+
472
+ /*
473
+ * What a refused caller is told. See getRefusalMessages for why the sign-in
474
+ * routes all share one pair and the resend route has its own.
475
+ */
476
+ export const IDENTITY_RATE_LIMITED_MESSAGE: string =
477
+ "Too many sign-in attempts. Please try again later.";
478
+ export const IDENTITY_COUNTER_UNAVAILABLE_MESSAGE: string =
479
+ "Unable to sign you in right now. Please try again shortly.";
480
+ export const VERIFICATION_EMAIL_RESEND_RATE_LIMITED_MESSAGE: string =
481
+ "Too many requests for a new verification email. Please try again later.";
482
+ export const VERIFICATION_EMAIL_RESEND_UNAVAILABLE_MESSAGE: string =
483
+ "Unable to send a verification email right now. Please try again shortly.";
484
+
416
485
  const KEY_PREFIX: string = "identity:rl:";
417
486
 
418
487
  /*
@@ -574,9 +643,44 @@ export default class IdentityRateLimit {
574
643
  return STATUS_PAGE_LOGIN_BUCKET;
575
644
  }
576
645
 
646
+ if (bucket === IdentityRateLimitBucket.VerificationEmailResend) {
647
+ return VERIFICATION_EMAIL_RESEND_BUCKET;
648
+ }
649
+
577
650
  return LOGIN_BUCKET;
578
651
  }
579
652
 
653
+ /*
654
+ * What a refused request is told, for the 429 and for the fail-closed 503.
655
+ *
656
+ * Every sign-in bucket gets the same pair, byte for byte, whichever counter
657
+ * fired and whichever route was hit -- see the note in getMiddleware on why
658
+ * a sign-in limiter must not answer differently for different accounts.
659
+ *
660
+ * The resend route gets its own pair because the sign-in wording is simply
661
+ * wrong there: somebody who pressed "resend verification email" has made no
662
+ * sign-in attempt, and telling them they made too many would send them
663
+ * looking for a problem they do not have. The pair is still uniform WITHIN
664
+ * the route: the same text for every caller, token and account, so it
665
+ * discloses nothing the sign-in pair does not.
666
+ */
667
+ public static getRefusalMessages(bucket: IdentityRateLimitBucket): {
668
+ rateLimited: string;
669
+ unavailable: string;
670
+ } {
671
+ if (bucket === IdentityRateLimitBucket.VerificationEmailResend) {
672
+ return {
673
+ rateLimited: VERIFICATION_EMAIL_RESEND_RATE_LIMITED_MESSAGE,
674
+ unavailable: VERIFICATION_EMAIL_RESEND_UNAVAILABLE_MESSAGE,
675
+ };
676
+ }
677
+
678
+ return {
679
+ rateLimited: IDENTITY_RATE_LIMITED_MESSAGE,
680
+ unavailable: IDENTITY_COUNTER_UNAVAILABLE_MESSAGE,
681
+ };
682
+ }
683
+
580
684
  /*
581
685
  * Increment both counters for this request and decide.
582
686
  *
@@ -773,13 +877,14 @@ export default class IdentityRateLimit {
773
877
  * account exists. These routes are careful not to say which half of a
774
878
  * credential was wrong; a limiter that answered differently for a real
775
879
  * address than for an invented one would hand back the account
776
- * enumeration the handlers withhold.
880
+ * enumeration the handlers withhold. (The resend route has wording of
881
+ * its own, but it is just as uniform; see getRefusalMessages.)
777
882
  */
778
883
  return Response.sendErrorResponse(
779
884
  req,
780
885
  res,
781
886
  new TooManyRequestsException(
782
- "Too many sign-in attempts. Please try again later.",
887
+ IdentityRateLimit.getRefusalMessages(bucket).rateLimited,
783
888
  ),
784
889
  );
785
890
  }
@@ -800,7 +905,7 @@ export default class IdentityRateLimit {
800
905
  req,
801
906
  res,
802
907
  new ServiceUnavailableException(
803
- "Unable to sign you in right now. Please try again shortly.",
908
+ IdentityRateLimit.getRefusalMessages(bucket).unavailable,
804
909
  ),
805
910
  );
806
911
  }
@@ -0,0 +1,237 @@
1
+ import Redis, { ClientType } from "../Infrastructure/Redis";
2
+ import {
3
+ ExpressRequest,
4
+ ExpressResponse,
5
+ NextFunction,
6
+ } from "../Utils/Express";
7
+ import logger from "../Utils/Logger";
8
+ import Response from "../Utils/Response";
9
+ import TooManyRequestsException from "../../Types/Exception/TooManyRequestsException";
10
+ import SubscriberNotificationTestSendRateLimit from "./SubscriberNotificationTestSendRateLimit";
11
+
12
+ /*
13
+ * Request limiting for "Preview notification" (POST /preview on the
14
+ * subscriber notification preview router).
15
+ *
16
+ * A preview sends nothing, but it is not free: it renders the incident's
17
+ * description and custom fields once, then an email for every status page
18
+ * the caller can read, and returns all of that HTML in one answer. Any
19
+ * project member holding one of the audience roles may call it, as often as
20
+ * they like, so this bounds how much rendering one person can ask the App
21
+ * server for. The request itself is bounded too
22
+ * (SubscriberNotificationPreviewBuilder.parseRequest caps the text and the
23
+ * custom field values).
24
+ *
25
+ * The limit is loose: someone writing an incident or a note and previewing
26
+ * it after each change is nowhere near it. The counter is per user, across
27
+ * every project and incident, like the test email's
28
+ * (SubscriberNotificationTestSendRateLimit, whose user key this shares).
29
+ *
30
+ * THIS FAILS OPEN
31
+ *
32
+ * If Redis is unreachable the preview is served. That is the opposite of the
33
+ * test email's choice, and the difference is what is at stake: a test email
34
+ * is real mail, and this counter would be the only bound on it; a preview
35
+ * sends nothing, its request is size-capped, and refusing it would take the
36
+ * preview away from everyone for as long as Redis is down.
37
+ */
38
+
39
+ export enum SubscriberNotificationPreviewRateLimitOutcome {
40
+ Allowed = "allowed",
41
+ RateLimited = "rate-limited",
42
+
43
+ // Redis is unreachable: served, since this fails open.
44
+ CounterUnavailable = "counter-unavailable",
45
+ }
46
+
47
+ export interface SubscriberNotificationPreviewRateLimitDecision {
48
+ outcome: SubscriberNotificationPreviewRateLimitOutcome;
49
+ retryAfterSeconds?: number | undefined;
50
+ // True only on the request that first crossed the line in this window.
51
+ isFirstRejectionInWindow?: boolean | undefined;
52
+ }
53
+
54
+ export interface SubscriberNotificationPreviewRateLimitConfig {
55
+ windowSeconds: number;
56
+ perUserLimit: number;
57
+ }
58
+
59
+ const KEY_PREFIX: string = "subnotifpreview:rl:";
60
+
61
+ // Counter keys outlive their window by one full window.
62
+ const TTL_MULTIPLIER: number = 2;
63
+
64
+ // How often the "counter unavailable" condition may be logged.
65
+ const COUNTER_UNAVAILABLE_LOG_INTERVAL_MS: number = 60 * 1000;
66
+
67
+ export default class SubscriberNotificationPreviewRateLimit {
68
+ // Sixty previews every ten minutes: one every ten seconds, sustained.
69
+ private static readonly config: SubscriberNotificationPreviewRateLimitConfig =
70
+ {
71
+ windowSeconds: 10 * 60,
72
+ perUserLimit: 60,
73
+ };
74
+
75
+ private static counterUnavailableLastLoggedAt: number = 0;
76
+
77
+ // The limit in force, for tests and for operators' diagnostics.
78
+ public static getConfig(): SubscriberNotificationPreviewRateLimitConfig {
79
+ return { ...SubscriberNotificationPreviewRateLimit.config };
80
+ }
81
+
82
+ /*
83
+ * Count this request against the user and decide. A rejected request is
84
+ * counted too, so a caller who keeps trying keeps their window pinned.
85
+ */
86
+ public static async consume(data: {
87
+ userKey: string;
88
+ }): Promise<SubscriberNotificationPreviewRateLimitDecision> {
89
+ const client: ClientType | null = Redis.getClient();
90
+
91
+ if (!client || !Redis.isConnected()) {
92
+ return {
93
+ outcome:
94
+ SubscriberNotificationPreviewRateLimitOutcome.CounterUnavailable,
95
+ };
96
+ }
97
+
98
+ const config: SubscriberNotificationPreviewRateLimitConfig =
99
+ SubscriberNotificationPreviewRateLimit.config;
100
+ const windowMs: number = config.windowSeconds * 1000;
101
+ const windowIndex: number = Math.floor(Date.now() / windowMs);
102
+ const counterKey: string = `${KEY_PREFIX}u:${data.userKey}:${windowIndex}`;
103
+
104
+ try {
105
+ const count: number = await client.incr(counterKey);
106
+
107
+ if (typeof count !== "number") {
108
+ throw new Error("Rate limit counter returned a non-numeric value");
109
+ }
110
+
111
+ // Set only by the request that created the key, so the window is fixed.
112
+ if (count === 1) {
113
+ await client.expire(counterKey, config.windowSeconds * TTL_MULTIPLIER);
114
+ }
115
+
116
+ if (count > config.perUserLimit) {
117
+ return {
118
+ outcome: SubscriberNotificationPreviewRateLimitOutcome.RateLimited,
119
+ retryAfterSeconds:
120
+ SubscriberNotificationTestSendRateLimit.getSecondsUntilWindowEnd(
121
+ config.windowSeconds,
122
+ ),
123
+ isFirstRejectionInWindow: count === config.perUserLimit + 1,
124
+ };
125
+ }
126
+
127
+ return {
128
+ outcome: SubscriberNotificationPreviewRateLimitOutcome.Allowed,
129
+ };
130
+ } catch (err) {
131
+ if (
132
+ SubscriberNotificationPreviewRateLimit.shouldLogCounterUnavailable()
133
+ ) {
134
+ logger.warn(
135
+ `SubscriberNotificationPreviewRateLimit: counter failed for user ${data.userKey}`,
136
+ );
137
+ logger.warn(err);
138
+ }
139
+
140
+ return {
141
+ outcome:
142
+ SubscriberNotificationPreviewRateLimitOutcome.CounterUnavailable,
143
+ };
144
+ }
145
+ }
146
+
147
+ /*
148
+ * Express middleware. Register after UserMiddleware (the counter is per
149
+ * user) and before the handler, so a flood is refused before it renders
150
+ * anything.
151
+ */
152
+ public static getMiddleware(): (
153
+ req: ExpressRequest,
154
+ res: ExpressResponse,
155
+ next: NextFunction,
156
+ ) => Promise<void> {
157
+ return async (
158
+ req: ExpressRequest,
159
+ res: ExpressResponse,
160
+ next: NextFunction,
161
+ ): Promise<void> => {
162
+ const userKey: string =
163
+ SubscriberNotificationTestSendRateLimit.resolveUserKey(req);
164
+
165
+ const decision: SubscriberNotificationPreviewRateLimitDecision =
166
+ await SubscriberNotificationPreviewRateLimit.consume({
167
+ userKey: userKey,
168
+ });
169
+
170
+ if (
171
+ decision.outcome ===
172
+ SubscriberNotificationPreviewRateLimitOutcome.RateLimited
173
+ ) {
174
+ if (decision.retryAfterSeconds) {
175
+ const setHeader: unknown = (
176
+ res as unknown as Record<string, unknown>
177
+ )["setHeader"];
178
+
179
+ if (typeof setHeader === "function") {
180
+ (setHeader as (name: string, value: string) => void).call(
181
+ res,
182
+ "Retry-After",
183
+ String(decision.retryAfterSeconds),
184
+ );
185
+ }
186
+ }
187
+
188
+ if (decision.isFirstRejectionInWindow) {
189
+ logger.warn(
190
+ `SubscriberNotificationPreviewRateLimit: rejected previews from user ${userKey} for the rest of this window`,
191
+ );
192
+ }
193
+
194
+ return Response.sendErrorResponse(
195
+ req,
196
+ res,
197
+ new TooManyRequestsException(
198
+ "You have previewed notifications too many times in a short while. Please try again in a few minutes.",
199
+ ),
200
+ );
201
+ }
202
+
203
+ if (
204
+ decision.outcome ===
205
+ SubscriberNotificationPreviewRateLimitOutcome.CounterUnavailable &&
206
+ SubscriberNotificationPreviewRateLimit.shouldLogCounterUnavailable()
207
+ ) {
208
+ // Fails open: see the note at the top of this file.
209
+ logger.warn(
210
+ "SubscriberNotificationPreviewRateLimit: rate limit counter unavailable, serving previews without a limit",
211
+ );
212
+ }
213
+
214
+ return next();
215
+ };
216
+ }
217
+
218
+ /*
219
+ * A Redis outage means every request takes the unavailable path; one log
220
+ * line per interval makes the condition visible without burying the rest.
221
+ */
222
+ private static shouldLogCounterUnavailable(): boolean {
223
+ const now: number = Date.now();
224
+
225
+ if (
226
+ now -
227
+ SubscriberNotificationPreviewRateLimit.counterUnavailableLastLoggedAt <
228
+ COUNTER_UNAVAILABLE_LOG_INTERVAL_MS
229
+ ) {
230
+ return false;
231
+ }
232
+
233
+ SubscriberNotificationPreviewRateLimit.counterUnavailableLastLoggedAt = now;
234
+
235
+ return true;
236
+ }
237
+ }
@@ -0,0 +1,304 @@
1
+ import Redis, { ClientType } from "../Infrastructure/Redis";
2
+ import {
3
+ ExpressRequest,
4
+ ExpressResponse,
5
+ NextFunction,
6
+ OneUptimeRequest,
7
+ } from "../Utils/Express";
8
+ import logger from "../Utils/Logger";
9
+ import Response from "../Utils/Response";
10
+ import ObjectID from "../../Types/ObjectID";
11
+ import ServiceUnavailableException from "../../Types/Exception/ServiceUnavailableException";
12
+ import TooManyRequestsException from "../../Types/Exception/TooManyRequestsException";
13
+
14
+ /*
15
+ * Request limiting for "Send test to me" (POST /send-test on the subscriber
16
+ * notification preview router), which emails the signed-in user the status
17
+ * page subscriber email they are previewing.
18
+ *
19
+ * The route only ever sends to the caller's own verified account email, so
20
+ * what this bounds is volume: every allowed request is a real email, through
21
+ * the status page's own SMTP server or the instance's mail provider, and
22
+ * nothing else stops a script (or a held-down button) from sending hundreds.
23
+ * The counter is per user, across every project, status page and incident:
24
+ * a user is one inbox.
25
+ *
26
+ * THIS FAILS CLOSED
27
+ *
28
+ * If Redis is unreachable, the test is refused with "try again later". That
29
+ * is the opposite of the choice VerificationCodeRateLimit makes, and the
30
+ * difference is what else bounds the requests: there, the code's expiry, its
31
+ * attempt counter and the resend cooldown all live in Postgres and stay in
32
+ * force; here this counter is the only bound on how much mail is sent. A
33
+ * test email is a convenience, and a Redis blip costs nothing but a retry.
34
+ */
35
+
36
+ export enum SubscriberNotificationTestSendRateLimitOutcome {
37
+ Allowed = "allowed",
38
+ RateLimited = "rate-limited",
39
+
40
+ // Redis is unreachable, so the limit cannot be honoured.
41
+ CounterUnavailable = "counter-unavailable",
42
+ }
43
+
44
+ export interface SubscriberNotificationTestSendRateLimitDecision {
45
+ outcome: SubscriberNotificationTestSendRateLimitOutcome;
46
+ retryAfterSeconds?: number | undefined;
47
+ /*
48
+ * True only on the request that first crossed the line in this window, so
49
+ * a flood produces one log line per user per window.
50
+ */
51
+ isFirstRejectionInWindow?: boolean | undefined;
52
+ }
53
+
54
+ export interface SubscriberNotificationTestSendRateLimitConfig {
55
+ windowSeconds: number;
56
+ perUserLimit: number;
57
+ }
58
+
59
+ const KEY_PREFIX: string = "subnotiftest:rl:";
60
+
61
+ /*
62
+ * Counter keys outlive their window by one full window, so a request landing
63
+ * on a boundary cannot read a key that was reclaimed mid-window.
64
+ */
65
+ const TTL_MULTIPLIER: number = 2;
66
+
67
+ // How often the "counter unavailable" condition may be logged.
68
+ const COUNTER_UNAVAILABLE_LOG_INTERVAL_MS: number = 60 * 1000;
69
+
70
+ export default class SubscriberNotificationTestSendRateLimit {
71
+ /*
72
+ * Ten test emails a quarter of an hour: someone checking each status
73
+ * page's email in turn, or trying a template change a few times, is
74
+ * nowhere near it.
75
+ */
76
+ private static readonly config: SubscriberNotificationTestSendRateLimitConfig =
77
+ {
78
+ windowSeconds: 15 * 60,
79
+ perUserLimit: 10,
80
+ };
81
+
82
+ private static counterUnavailableLastLoggedAt: number = 0;
83
+
84
+ // The limit in force, for tests and for operators' diagnostics.
85
+ public static getConfig(): SubscriberNotificationTestSendRateLimitConfig {
86
+ return { ...SubscriberNotificationTestSendRateLimit.config };
87
+ }
88
+
89
+ /*
90
+ * The signed-in user the request is billed to. The route sits behind
91
+ * UserMiddleware.requireUserAuthentication, so there always is one; if
92
+ * somehow there is not, every such request shares one bucket rather than
93
+ * each getting its own.
94
+ */
95
+ public static resolveUserKey(req: ExpressRequest): string {
96
+ const userId: ObjectID | undefined = (req as OneUptimeRequest)
97
+ ?.userAuthorization?.userId;
98
+
99
+ if (!userId) {
100
+ return "anonymous";
101
+ }
102
+
103
+ const key: string = userId.toString().toLowerCase();
104
+
105
+ return ObjectID.isValidUUID(key) ? key : "invalid";
106
+ }
107
+
108
+ /*
109
+ * Count this request against the user and decide. The counter is
110
+ * incremented on every request, a rejected one included, so a caller who
111
+ * keeps trying keeps their window pinned rather than getting a fresh
112
+ * allowance for free.
113
+ */
114
+ public static async consume(data: {
115
+ userKey: string;
116
+ }): Promise<SubscriberNotificationTestSendRateLimitDecision> {
117
+ const client: ClientType | null = Redis.getClient();
118
+
119
+ if (!client || !Redis.isConnected()) {
120
+ return {
121
+ outcome:
122
+ SubscriberNotificationTestSendRateLimitOutcome.CounterUnavailable,
123
+ };
124
+ }
125
+
126
+ const config: SubscriberNotificationTestSendRateLimitConfig =
127
+ SubscriberNotificationTestSendRateLimit.config;
128
+ const windowMs: number = config.windowSeconds * 1000;
129
+ const windowIndex: number = Math.floor(Date.now() / windowMs);
130
+ const counterKey: string = `${KEY_PREFIX}u:${data.userKey}:${windowIndex}`;
131
+
132
+ try {
133
+ const count: number = await client.incr(counterKey);
134
+
135
+ if (typeof count !== "number") {
136
+ throw new Error("Rate limit counter returned a non-numeric value");
137
+ }
138
+
139
+ /*
140
+ * The expiry is set only by the request that created the key: setting
141
+ * it on every increment would slide the window for as long as the
142
+ * requests keep coming.
143
+ */
144
+ if (count === 1) {
145
+ await client.expire(counterKey, config.windowSeconds * TTL_MULTIPLIER);
146
+ }
147
+
148
+ if (count > config.perUserLimit) {
149
+ return {
150
+ outcome: SubscriberNotificationTestSendRateLimitOutcome.RateLimited,
151
+ retryAfterSeconds:
152
+ SubscriberNotificationTestSendRateLimit.getSecondsUntilWindowEnd(
153
+ config.windowSeconds,
154
+ ),
155
+ isFirstRejectionInWindow: count === config.perUserLimit + 1,
156
+ };
157
+ }
158
+
159
+ return {
160
+ outcome: SubscriberNotificationTestSendRateLimitOutcome.Allowed,
161
+ };
162
+ } catch (err) {
163
+ if (
164
+ SubscriberNotificationTestSendRateLimit.shouldLogCounterUnavailable()
165
+ ) {
166
+ logger.warn(
167
+ `SubscriberNotificationTestSendRateLimit: counter failed for user ${data.userKey}`,
168
+ );
169
+ logger.warn(err);
170
+ }
171
+
172
+ return {
173
+ outcome:
174
+ SubscriberNotificationTestSendRateLimitOutcome.CounterUnavailable,
175
+ };
176
+ }
177
+ }
178
+
179
+ /*
180
+ * Express middleware. Register after UserMiddleware (the counter is per
181
+ * user) and before the handler, so a flood is refused before it reads a
182
+ * single row.
183
+ */
184
+ public static getMiddleware(): (
185
+ req: ExpressRequest,
186
+ res: ExpressResponse,
187
+ next: NextFunction,
188
+ ) => Promise<void> {
189
+ return async (
190
+ req: ExpressRequest,
191
+ res: ExpressResponse,
192
+ next: NextFunction,
193
+ ): Promise<void> => {
194
+ const userKey: string =
195
+ SubscriberNotificationTestSendRateLimit.resolveUserKey(req);
196
+
197
+ const decision: SubscriberNotificationTestSendRateLimitDecision =
198
+ await SubscriberNotificationTestSendRateLimit.consume({
199
+ userKey: userKey,
200
+ });
201
+
202
+ if (
203
+ decision.outcome ===
204
+ SubscriberNotificationTestSendRateLimitOutcome.RateLimited
205
+ ) {
206
+ if (decision.retryAfterSeconds) {
207
+ SubscriberNotificationTestSendRateLimit.setRetryAfterHeader(
208
+ res,
209
+ decision.retryAfterSeconds,
210
+ );
211
+ }
212
+
213
+ if (decision.isFirstRejectionInWindow) {
214
+ logger.warn(
215
+ `SubscriberNotificationTestSendRateLimit: rejected test emails from user ${userKey} for the rest of this window`,
216
+ );
217
+ }
218
+
219
+ return Response.sendErrorResponse(
220
+ req,
221
+ res,
222
+ new TooManyRequestsException(
223
+ "You have sent yourself too many test emails. Please try again later.",
224
+ ),
225
+ );
226
+ }
227
+
228
+ if (
229
+ decision.outcome ===
230
+ SubscriberNotificationTestSendRateLimitOutcome.CounterUnavailable
231
+ ) {
232
+ // Fails closed: see the note at the top of this file.
233
+ if (
234
+ SubscriberNotificationTestSendRateLimit.shouldLogCounterUnavailable()
235
+ ) {
236
+ logger.warn(
237
+ "SubscriberNotificationTestSendRateLimit: rate limit counter unavailable, refusing test emails",
238
+ );
239
+ }
240
+
241
+ return Response.sendErrorResponse(
242
+ req,
243
+ res,
244
+ new ServiceUnavailableException(
245
+ "Test emails cannot be sent right now. Please try again in a few minutes.",
246
+ ),
247
+ );
248
+ }
249
+
250
+ return next();
251
+ };
252
+ }
253
+
254
+ /*
255
+ * Remaining seconds in the current fixed window, so a refused caller is
256
+ * told to come back when the window actually rolls. The preview limiter
257
+ * (SubscriberNotificationPreviewRateLimit) counts in the same fixed
258
+ * windows, and uses this too.
259
+ */
260
+ public static getSecondsUntilWindowEnd(windowSeconds: number): number {
261
+ const windowMs: number = windowSeconds * 1000;
262
+ const msIntoWindow: number = Date.now() % windowMs;
263
+
264
+ return Math.max(1, Math.ceil((windowMs - msIntoWindow) / 1000));
265
+ }
266
+
267
+ /*
268
+ * A Redis outage means every request takes the unavailable path; one log
269
+ * line per interval makes the condition visible without burying the rest.
270
+ */
271
+ private static shouldLogCounterUnavailable(): boolean {
272
+ const now: number = Date.now();
273
+
274
+ if (
275
+ now -
276
+ SubscriberNotificationTestSendRateLimit.counterUnavailableLastLoggedAt <
277
+ COUNTER_UNAVAILABLE_LOG_INTERVAL_MS
278
+ ) {
279
+ return false;
280
+ }
281
+
282
+ SubscriberNotificationTestSendRateLimit.counterUnavailableLastLoggedAt =
283
+ now;
284
+
285
+ return true;
286
+ }
287
+
288
+ private static setRetryAfterHeader(
289
+ res: ExpressResponse,
290
+ retryAfterSeconds: number,
291
+ ): void {
292
+ const setHeader: unknown = (res as unknown as Record<string, unknown>)[
293
+ "setHeader"
294
+ ];
295
+
296
+ if (typeof setHeader === "function") {
297
+ (setHeader as (name: string, value: string) => void).call(
298
+ res,
299
+ "Retry-After",
300
+ String(retryAfterSeconds),
301
+ );
302
+ }
303
+ }
304
+ }