@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
@@ -24,7 +24,10 @@ import {
24
24
  * a tripped limit a permanent lockout;
25
25
  * - the buckets are separate, so failing the password step cannot spend the
26
26
  * recovery step's budget;
27
- * - a missing counter fails CLOSED, unlike the verification-code limiter.
27
+ * - a missing counter fails CLOSED, unlike the verification-code limiter;
28
+ * - POST /resend-verification-email, which sends mail rather than checking a
29
+ * credential, has a budget, a key namespace and refusal wording of its own
30
+ * -- while every sign-in bucket keeps its wording byte for byte.
28
31
  *
29
32
  * Each has its own describe block below.
30
33
  */
@@ -65,10 +68,14 @@ import Redis from "../../../Server/Infrastructure/Redis";
65
68
  import Response from "../../../Server/Utils/Response";
66
69
  import logger from "../../../Server/Utils/Logger";
67
70
  import IdentityRateLimit, {
71
+ IDENTITY_COUNTER_UNAVAILABLE_MESSAGE,
72
+ IDENTITY_RATE_LIMITED_MESSAGE,
68
73
  IdentityRateLimitBucket,
69
74
  IdentityRateLimitDecision,
70
75
  IdentityRateLimitOutcome,
71
76
  IdentityRateLimitScope,
77
+ VERIFICATION_EMAIL_RESEND_RATE_LIMITED_MESSAGE,
78
+ VERIFICATION_EMAIL_RESEND_UNAVAILABLE_MESSAGE,
72
79
  } from "../../../Server/Middleware/IdentityRateLimit";
73
80
  import Exception from "../../../Types/Exception/Exception";
74
81
  import ExceptionCode from "../../../Types/Exception/ExceptionCode";
@@ -578,6 +585,47 @@ describe("IdentityRateLimit", () => {
578
585
  IdentityRateLimit.getBucketConfig(IdentityRateLimitBucket.Login),
579
586
  );
580
587
  });
588
+
589
+ /*
590
+ * Twenty a quarter hour, on BOTH counters. The resend request carries no
591
+ * email address, so the account counter is keyed on the client address
592
+ * alone and is the same budget as the address counter.
593
+ */
594
+ it("ships the documented verification-email-resend budget", () => {
595
+ expect(
596
+ IdentityRateLimit.getBucketConfig(
597
+ IdentityRateLimitBucket.VerificationEmailResend,
598
+ ),
599
+ ).toEqual({
600
+ windowSeconds: 15 * 60,
601
+ perAccountLimit: 20,
602
+ perIpLimit: 20,
603
+ });
604
+ });
605
+
606
+ /*
607
+ * getBucketConfig ends in `return LOGIN_BUCKET`, so a bucket with no
608
+ * branch of its own is silently handed the password budget. For this one
609
+ * the numbers differ, so the fallthrough would show -- but pin the
610
+ * identity too, so nobody "fixes" the numbers by pointing it at login.
611
+ */
612
+ it("hands the resend bucket its own budget, not the login one it would fall through to", () => {
613
+ const resendConfig: ReturnType<typeof IdentityRateLimit.getBucketConfig> =
614
+ IdentityRateLimit.getBucketConfig(
615
+ IdentityRateLimitBucket.VerificationEmailResend,
616
+ );
617
+ const loginConfig: ReturnType<typeof IdentityRateLimit.getBucketConfig> =
618
+ IdentityRateLimit.getBucketConfig(IdentityRateLimitBucket.Login);
619
+
620
+ expect(resendConfig).not.toBe(loginConfig);
621
+ expect(resendConfig).not.toEqual(loginConfig);
622
+ });
623
+
624
+ it("names the resend bucket with a stable key segment", () => {
625
+ expect(IdentityRateLimitBucket.VerificationEmailResend).toBe(
626
+ "verification-email-resend",
627
+ );
628
+ });
581
629
  });
582
630
 
583
631
  describe("the per-account counter", () => {
@@ -1212,5 +1260,507 @@ describe("IdentityRateLimit", () => {
1212
1260
  expect(accountKeys).toHaveLength(1);
1213
1261
  expect(client.counters.get(accountKeys[0] as string)).toBe(2);
1214
1262
  });
1263
+
1264
+ /*
1265
+ * The refusals a resend request actually receives, through the real
1266
+ * middleware: its own wording on the 429 and the 503, and the sign-in
1267
+ * routes' wording untouched beside it.
1268
+ */
1269
+ const resendRequest: () => ExpressRequest = (): ExpressRequest => {
1270
+ return buildRequest({
1271
+ body: { data: { resendToken: "v1.payload.signature" } },
1272
+ headers: { "x-forwarded-for": "203.0.113.7" },
1273
+ });
1274
+ };
1275
+
1276
+ it("answers a spent resend budget with 429 and the resend wording", async () => {
1277
+ let headers: Record<string, string> = {};
1278
+ let nextCalled: boolean = true;
1279
+
1280
+ for (let i: number = 0; i < 21; i++) {
1281
+ const result: {
1282
+ nextCalled: boolean;
1283
+ headers: Record<string, string>;
1284
+ } = await runMiddleware({
1285
+ bucket: IdentityRateLimitBucket.VerificationEmailResend,
1286
+ request: resendRequest(),
1287
+ });
1288
+
1289
+ headers = result.headers;
1290
+ nextCalled = result.nextCalled;
1291
+ }
1292
+
1293
+ expect(nextCalled).toBe(false);
1294
+ expect(sendErrorResponseMock).toHaveBeenCalledTimes(1);
1295
+
1296
+ const error: Exception = sendErrorResponseMock.mock
1297
+ .calls[0]?.[2] as Exception;
1298
+
1299
+ expect(error.code).toBe(ExceptionCode.TooManyRequestsException);
1300
+ expect(error.message).toBe(
1301
+ "Too many requests for a new verification email. Please try again later.",
1302
+ );
1303
+ expect(Number(headers["Retry-After"])).toBeGreaterThan(0);
1304
+ });
1305
+
1306
+ it("fails closed on the resend route with 503 and the resend wording", async () => {
1307
+ isConnectedMock.mockReturnValue(false);
1308
+
1309
+ const result: { nextCalled: boolean } = await runMiddleware({
1310
+ bucket: IdentityRateLimitBucket.VerificationEmailResend,
1311
+ request: resendRequest(),
1312
+ });
1313
+
1314
+ expect(result.nextCalled).toBe(false);
1315
+
1316
+ const error: Exception = sendErrorResponseMock.mock
1317
+ .calls[0]?.[2] as Exception;
1318
+
1319
+ expect(error.code).toBe(ExceptionCode.ServiceUnavailableException);
1320
+ expect(error.message).toBe(
1321
+ "Unable to send a verification email right now. Please try again shortly.",
1322
+ );
1323
+ });
1324
+
1325
+ it("still answers a spent sign-in budget with the sign-in wording", async () => {
1326
+ for (let i: number = 0; i < PER_ACCOUNT_LIMIT + 1; i++) {
1327
+ await runMiddleware({});
1328
+ }
1329
+
1330
+ const error: Exception = sendErrorResponseMock.mock
1331
+ .calls[0]?.[2] as Exception;
1332
+
1333
+ expect(error.code).toBe(ExceptionCode.TooManyRequestsException);
1334
+ expect(error.message).toBe(
1335
+ "Too many sign-in attempts. Please try again later.",
1336
+ );
1337
+ });
1338
+
1339
+ it("still fails closed on the sign-in routes with the sign-in wording", async () => {
1340
+ isConnectedMock.mockReturnValue(false);
1341
+
1342
+ await runMiddleware({ bucket: IdentityRateLimitBucket.TwoFactor });
1343
+
1344
+ const error: Exception = sendErrorResponseMock.mock
1345
+ .calls[0]?.[2] as Exception;
1346
+
1347
+ expect(error.code).toBe(ExceptionCode.ServiceUnavailableException);
1348
+ expect(error.message).toBe(
1349
+ "Unable to sign you in right now. Please try again shortly.",
1350
+ );
1351
+ });
1352
+
1353
+ it("says the same thing on the resend route whichever counter fired", async () => {
1354
+ // The address counter, reached by inventing an email field per request.
1355
+ for (let i: number = 0; i < 21; i++) {
1356
+ await runMiddleware({
1357
+ bucket: IdentityRateLimitBucket.VerificationEmailResend,
1358
+ request: buildRequest({
1359
+ body: { data: { email: `invented-${i}@example.com` } },
1360
+ headers: { "x-forwarded-for": "203.0.113.7" },
1361
+ }),
1362
+ });
1363
+ }
1364
+
1365
+ const ipMessage: string = (
1366
+ sendErrorResponseMock.mock.calls[0]?.[2] as Exception
1367
+ ).message;
1368
+
1369
+ sendErrorResponseMock.mockClear();
1370
+ client.reset();
1371
+
1372
+ // The account counter, which for this route is the address too.
1373
+ for (let i: number = 0; i < 21; i++) {
1374
+ await runMiddleware({
1375
+ bucket: IdentityRateLimitBucket.VerificationEmailResend,
1376
+ request: resendRequest(),
1377
+ });
1378
+ }
1379
+
1380
+ const accountMessage: string = (
1381
+ sendErrorResponseMock.mock.calls[0]?.[2] as Exception
1382
+ ).message;
1383
+
1384
+ expect(ipMessage).toBe(VERIFICATION_EMAIL_RESEND_RATE_LIMITED_MESSAGE);
1385
+ expect(accountMessage).toBe(ipMessage);
1386
+ });
1387
+ });
1388
+
1389
+ describe("the verification email resend bucket", () => {
1390
+ const RESEND_LIMIT: number = 20;
1391
+ const RESEND_WINDOW_SECONDS: number = 15 * 60;
1392
+
1393
+ const consumeResend: (
1394
+ accountKey?: string,
1395
+ ) => Promise<IdentityRateLimitDecision> = (
1396
+ accountKey?: string,
1397
+ ): Promise<IdentityRateLimitDecision> => {
1398
+ return consume({
1399
+ // The route's body carries no address: resolveAccountKey says "none".
1400
+ accountKey: accountKey || "none",
1401
+ bucket: IdentityRateLimitBucket.VerificationEmailResend,
1402
+ });
1403
+ };
1404
+
1405
+ it("allows twenty requests from one address and refuses the twenty-first", async () => {
1406
+ for (let i: number = 0; i < RESEND_LIMIT; i++) {
1407
+ expect((await consumeResend()).outcome).toBe(
1408
+ IdentityRateLimitOutcome.Allowed,
1409
+ );
1410
+ }
1411
+
1412
+ const refused: IdentityRateLimitDecision = await consumeResend();
1413
+
1414
+ expect(refused.outcome).toBe(IdentityRateLimitOutcome.RateLimited);
1415
+ expect(refused.retryAfterSeconds).toBeGreaterThan(0);
1416
+ });
1417
+
1418
+ /*
1419
+ * The resend body has no address, but nothing stops a caller adding one.
1420
+ * Rotating it buys a fresh account counter each time -- and still runs
1421
+ * into the same twenty on the address counter.
1422
+ */
1423
+ it("holds a caller who invents an email field per request to the same twenty", async () => {
1424
+ for (let i: number = 0; i < RESEND_LIMIT; i++) {
1425
+ expect((await consumeResend(`invented-${i}@example.com`)).outcome).toBe(
1426
+ IdentityRateLimitOutcome.Allowed,
1427
+ );
1428
+ }
1429
+
1430
+ const refused: IdentityRateLimitDecision = await consumeResend(
1431
+ "one-more@example.com",
1432
+ );
1433
+
1434
+ expect(refused.outcome).toBe(IdentityRateLimitOutcome.RateLimited);
1435
+ expect(refused.scope).toBe(IdentityRateLimitScope.Ip);
1436
+ });
1437
+
1438
+ it("keeps its counters under a key namespace of its own", async () => {
1439
+ await consumeResend();
1440
+
1441
+ const resendKeys: Array<string> = client.keysMatching(
1442
+ ":verification-email-resend:",
1443
+ );
1444
+
1445
+ expect(resendKeys).toHaveLength(2);
1446
+
1447
+ for (const key of resendKeys) {
1448
+ expect(key.startsWith("identity:rl:verification-email-resend:")).toBe(
1449
+ true,
1450
+ );
1451
+ }
1452
+
1453
+ expect(client.keysMatching(":login:")).toHaveLength(0);
1454
+ expect(client.keysMatching(":passkey:")).toHaveLength(0);
1455
+ });
1456
+
1457
+ it("expires its counters after two of its own windows", async () => {
1458
+ await consumeResend();
1459
+
1460
+ for (const key of client.keysMatching(":verification-email-resend:")) {
1461
+ expect(client.expiresForKey(key)).toEqual([
1462
+ { key, ttlSeconds: RESEND_WINDOW_SECONDS * TTL_MULTIPLIER },
1463
+ ]);
1464
+ }
1465
+ });
1466
+
1467
+ /*
1468
+ * Somebody on the verify-your-email page is about to sign in. Pressing
1469
+ * "resend" must not spend the attempts they are about to need, and a burst
1470
+ * of sign-in noise from their office must not refuse the resend.
1471
+ */
1472
+ it("does not spend the sign-in budget", async () => {
1473
+ for (let i: number = 0; i < RESEND_LIMIT + 1; i++) {
1474
+ await consume({
1475
+ bucket: IdentityRateLimitBucket.VerificationEmailResend,
1476
+ });
1477
+ }
1478
+
1479
+ expect(
1480
+ (await consume({ bucket: IdentityRateLimitBucket.Login })).outcome,
1481
+ ).toBe(IdentityRateLimitOutcome.Allowed);
1482
+ });
1483
+
1484
+ it("is not spent by sign-in attempts", async () => {
1485
+ for (let i: number = 0; i < PER_IP_LIMIT + 1; i++) {
1486
+ await consume({
1487
+ accountKey: `rotating-${i}@example.com`,
1488
+ bucket: IdentityRateLimitBucket.Login,
1489
+ });
1490
+ }
1491
+
1492
+ expect((await consumeResend()).outcome).toBe(
1493
+ IdentityRateLimitOutcome.Allowed,
1494
+ );
1495
+ });
1496
+
1497
+ it("lets the caller back in once the window rolls", async () => {
1498
+ for (let i: number = 0; i < RESEND_LIMIT + 1; i++) {
1499
+ await consumeResend();
1500
+ }
1501
+
1502
+ currentTime = currentTime + RESEND_WINDOW_SECONDS * 1000;
1503
+
1504
+ expect((await consumeResend()).outcome).toBe(
1505
+ IdentityRateLimitOutcome.Allowed,
1506
+ );
1507
+ });
1508
+ });
1509
+
1510
+ describe("getRefusalMessages", () => {
1511
+ const SIGN_IN_BUCKETS: Array<IdentityRateLimitBucket> = Object.values(
1512
+ IdentityRateLimitBucket,
1513
+ ).filter((bucket: IdentityRateLimitBucket) => {
1514
+ return bucket !== IdentityRateLimitBucket.VerificationEmailResend;
1515
+ });
1516
+
1517
+ /*
1518
+ * Byte for byte what these routes answered before the resend route had
1519
+ * wording of its own. Clients and translations key on these strings.
1520
+ */
1521
+ it("keeps the exact sign-in wording in the exported constants", () => {
1522
+ expect(IDENTITY_RATE_LIMITED_MESSAGE).toBe(
1523
+ "Too many sign-in attempts. Please try again later.",
1524
+ );
1525
+ expect(IDENTITY_COUNTER_UNAVAILABLE_MESSAGE).toBe(
1526
+ "Unable to sign you in right now. Please try again shortly.",
1527
+ );
1528
+ });
1529
+
1530
+ it("words the resend refusals about verification email, not sign-in", () => {
1531
+ expect(VERIFICATION_EMAIL_RESEND_RATE_LIMITED_MESSAGE).toBe(
1532
+ "Too many requests for a new verification email. Please try again later.",
1533
+ );
1534
+ expect(VERIFICATION_EMAIL_RESEND_UNAVAILABLE_MESSAGE).toBe(
1535
+ "Unable to send a verification email right now. Please try again shortly.",
1536
+ );
1537
+ });
1538
+
1539
+ it("knows every sign-in bucket there is", () => {
1540
+ /*
1541
+ * A floor, so the it.each below cannot pass by iterating nothing. Adding
1542
+ * a bucket grows this list and puts it under the assertions below.
1543
+ */
1544
+ expect(SIGN_IN_BUCKETS).toEqual(
1545
+ expect.arrayContaining([
1546
+ IdentityRateLimitBucket.Login,
1547
+ IdentityRateLimitBucket.TwoFactor,
1548
+ IdentityRateLimitBucket.BackupCode,
1549
+ IdentityRateLimitBucket.WebAuthnChallenge,
1550
+ IdentityRateLimitBucket.StatusPageLogin,
1551
+ IdentityRateLimitBucket.Passkey,
1552
+ ]),
1553
+ );
1554
+ expect(SIGN_IN_BUCKETS).toHaveLength(
1555
+ Object.values(IdentityRateLimitBucket).length - 1,
1556
+ );
1557
+ });
1558
+
1559
+ it.each(SIGN_IN_BUCKETS)(
1560
+ "answers the %s bucket with the unchanged sign-in wording",
1561
+ (bucket: IdentityRateLimitBucket) => {
1562
+ expect(IdentityRateLimit.getRefusalMessages(bucket)).toEqual({
1563
+ rateLimited: "Too many sign-in attempts. Please try again later.",
1564
+ unavailable:
1565
+ "Unable to sign you in right now. Please try again shortly.",
1566
+ });
1567
+ },
1568
+ );
1569
+
1570
+ it("answers the resend bucket with its own pair", () => {
1571
+ expect(
1572
+ IdentityRateLimit.getRefusalMessages(
1573
+ IdentityRateLimitBucket.VerificationEmailResend,
1574
+ ),
1575
+ ).toEqual({
1576
+ rateLimited:
1577
+ "Too many requests for a new verification email. Please try again later.",
1578
+ unavailable:
1579
+ "Unable to send a verification email right now. Please try again shortly.",
1580
+ });
1581
+ });
1582
+
1583
+ it("answers an unknown bucket with the sign-in wording", () => {
1584
+ expect(
1585
+ IdentityRateLimit.getRefusalMessages(
1586
+ "not-a-bucket" as IdentityRateLimitBucket,
1587
+ ),
1588
+ ).toEqual({
1589
+ rateLimited: IDENTITY_RATE_LIMITED_MESSAGE,
1590
+ unavailable: IDENTITY_COUNTER_UNAVAILABLE_MESSAGE,
1591
+ });
1592
+ });
1593
+
1594
+ it("never tells somebody pressing resend that they made sign-in attempts", () => {
1595
+ const messages: { rateLimited: string; unavailable: string } =
1596
+ IdentityRateLimit.getRefusalMessages(
1597
+ IdentityRateLimitBucket.VerificationEmailResend,
1598
+ );
1599
+
1600
+ expect(messages.rateLimited).not.toMatch(/sign/i);
1601
+ expect(messages.unavailable).not.toMatch(/sign/i);
1602
+ });
1603
+ });
1604
+ });
1605
+
1606
+ /*
1607
+ * The budgets are read from the environment once, at module load, so these
1608
+ * reload the module rather than calling into the already-configured one.
1609
+ */
1610
+ describe("the verification email resend bucket's configuration", () => {
1611
+ type BucketConfigShape = {
1612
+ windowSeconds: number;
1613
+ perAccountLimit: number;
1614
+ perIpLimit: number;
1615
+ };
1616
+
1617
+ type ReloadedConfigs = {
1618
+ resend: BucketConfigShape;
1619
+ login: BucketConfigShape;
1620
+ passkey: BucketConfigShape;
1621
+ };
1622
+
1623
+ interface IdentityRateLimitModule {
1624
+ default: {
1625
+ getBucketConfig: (bucket: IdentityRateLimitBucket) => BucketConfigShape;
1626
+ };
1627
+ }
1628
+
1629
+ const ENV_KEYS: Array<string> = [
1630
+ "IDENTITY_VERIFICATION_EMAIL_RESEND_RATE_LIMIT_WINDOW_SECONDS",
1631
+ "IDENTITY_VERIFICATION_EMAIL_RESEND_RATE_LIMIT_PER_IP_PER_WINDOW",
1632
+ "IDENTITY_LOGIN_RATE_LIMIT_WINDOW_SECONDS",
1633
+ "IDENTITY_LOGIN_RATE_LIMIT_PER_ACCOUNT_PER_WINDOW",
1634
+ "IDENTITY_LOGIN_RATE_LIMIT_PER_IP_PER_WINDOW",
1635
+ "IDENTITY_PASSKEY_RATE_LIMIT_WINDOW_SECONDS",
1636
+ "IDENTITY_PASSKEY_RATE_LIMIT_PER_IP_PER_WINDOW",
1637
+ ];
1638
+
1639
+ const configsUnderEnv: (env: Record<string, string>) => ReloadedConfigs = (
1640
+ env: Record<string, string>,
1641
+ ): ReloadedConfigs => {
1642
+ const previous: Record<string, string | undefined> = {};
1643
+
1644
+ for (const key of ENV_KEYS) {
1645
+ previous[key] = process.env[key];
1646
+
1647
+ const configured: string | undefined = env[key];
1648
+
1649
+ if (configured === undefined) {
1650
+ delete process.env[key];
1651
+ } else {
1652
+ process.env[key] = configured;
1653
+ }
1654
+ }
1655
+
1656
+ let configs: ReloadedConfigs | null = null;
1657
+
1658
+ try {
1659
+ jest.isolateModules((): void => {
1660
+ /* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires */
1661
+ const freshModule: IdentityRateLimitModule = require("../../../Server/Middleware/IdentityRateLimit");
1662
+ /* eslint-enable @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires */
1663
+
1664
+ configs = {
1665
+ resend: freshModule.default.getBucketConfig(
1666
+ IdentityRateLimitBucket.VerificationEmailResend,
1667
+ ),
1668
+ login: freshModule.default.getBucketConfig(
1669
+ IdentityRateLimitBucket.Login,
1670
+ ),
1671
+ passkey: freshModule.default.getBucketConfig(
1672
+ IdentityRateLimitBucket.Passkey,
1673
+ ),
1674
+ };
1675
+ });
1676
+ } finally {
1677
+ for (const key of ENV_KEYS) {
1678
+ const restored: string | undefined = previous[key];
1679
+
1680
+ if (restored === undefined) {
1681
+ delete process.env[key];
1682
+ } else {
1683
+ process.env[key] = restored;
1684
+ }
1685
+ }
1686
+ }
1687
+
1688
+ if (!configs) {
1689
+ throw new Error("The reloaded module produced no bucket configuration");
1690
+ }
1691
+
1692
+ return configs;
1693
+ };
1694
+
1695
+ it("falls back to the documented defaults when nothing is configured", () => {
1696
+ expect(configsUnderEnv({}).resend).toEqual({
1697
+ windowSeconds: 900,
1698
+ perAccountLimit: 20,
1699
+ perIpLimit: 20,
1700
+ });
1215
1701
  });
1702
+
1703
+ it("reads its window and its limit from its own settings", () => {
1704
+ const configs: ReloadedConfigs = configsUnderEnv({
1705
+ IDENTITY_VERIFICATION_EMAIL_RESEND_RATE_LIMIT_WINDOW_SECONDS: "321",
1706
+ IDENTITY_VERIFICATION_EMAIL_RESEND_RATE_LIMIT_PER_IP_PER_WINDOW: "7",
1707
+ });
1708
+
1709
+ // One limit, applied to both counters, because both key on the address.
1710
+ expect(configs.resend).toEqual({
1711
+ windowSeconds: 321,
1712
+ perAccountLimit: 7,
1713
+ perIpLimit: 7,
1714
+ });
1715
+
1716
+ // ...and nobody else's budget moves with it.
1717
+ expect(configs.login).toEqual({
1718
+ windowSeconds: 900,
1719
+ perAccountLimit: 10,
1720
+ perIpLimit: 150,
1721
+ });
1722
+ expect(configs.passkey).toEqual({
1723
+ windowSeconds: 900,
1724
+ perAccountLimit: 300,
1725
+ perIpLimit: 300,
1726
+ });
1727
+ });
1728
+
1729
+ it("does not move with the login or passkey settings", () => {
1730
+ const configs: ReloadedConfigs = configsUnderEnv({
1731
+ IDENTITY_LOGIN_RATE_LIMIT_WINDOW_SECONDS: "111",
1732
+ IDENTITY_LOGIN_RATE_LIMIT_PER_ACCOUNT_PER_WINDOW: "11",
1733
+ IDENTITY_LOGIN_RATE_LIMIT_PER_IP_PER_WINDOW: "1111",
1734
+ IDENTITY_PASSKEY_RATE_LIMIT_WINDOW_SECONDS: "222",
1735
+ IDENTITY_PASSKEY_RATE_LIMIT_PER_IP_PER_WINDOW: "2222",
1736
+ });
1737
+
1738
+ expect(configs.login).toEqual({
1739
+ windowSeconds: 111,
1740
+ perAccountLimit: 11,
1741
+ perIpLimit: 1111,
1742
+ });
1743
+ expect(configs.resend).toEqual({
1744
+ windowSeconds: 900,
1745
+ perAccountLimit: 20,
1746
+ perIpLimit: 20,
1747
+ });
1748
+ });
1749
+
1750
+ it.each(["0", "-5", "not-a-number", ""])(
1751
+ "ignores an unusable setting (%p) and keeps the default",
1752
+ (value: string) => {
1753
+ expect(
1754
+ configsUnderEnv({
1755
+ IDENTITY_VERIFICATION_EMAIL_RESEND_RATE_LIMIT_WINDOW_SECONDS: value,
1756
+ IDENTITY_VERIFICATION_EMAIL_RESEND_RATE_LIMIT_PER_IP_PER_WINDOW:
1757
+ value,
1758
+ }).resend,
1759
+ ).toEqual({
1760
+ windowSeconds: 900,
1761
+ perAccountLimit: 20,
1762
+ perIpLimit: 20,
1763
+ });
1764
+ },
1765
+ );
1216
1766
  });