@oneuptime/common 12.0.28 → 12.0.30

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 (367) hide show
  1. package/Models/DatabaseModels/DashboardDomain.ts +43 -0
  2. package/Models/DatabaseModels/Index.ts +11 -0
  3. package/Models/DatabaseModels/MonitorTemplate.ts +15 -3
  4. package/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +43 -0
  5. package/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.ts +662 -0
  6. package/Models/DatabaseModels/ProjectOnCallCalendarFeed.ts +584 -0
  7. package/Models/DatabaseModels/StatusPageDomain.ts +43 -0
  8. package/Models/DatabaseModels/UserOnCallCalendarFeed.ts +546 -0
  9. package/Models/DatabaseModels/UserOnCallShiftReminder.ts +231 -0
  10. package/Models/DatabaseModels/UserOnCallShiftReminderLog.ts +331 -0
  11. package/Server/API/DashboardDomainAPI.ts +74 -0
  12. package/Server/API/MicrosoftTeamsAPI.ts +81 -180
  13. package/Server/API/OnCallCalendarAPI.ts +2194 -0
  14. package/Server/API/StatusPageDomainAPI.ts +74 -0
  15. package/Server/EnvironmentConfig.ts +138 -0
  16. package/Server/Infrastructure/OnCallCalendarFeedCache.ts +1050 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.ts +43 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.ts +40 -0
  19. package/Server/Infrastructure/Postgres/SchemaMigrations/1790400000000-AddOnCallCalendarFeeds.ts +253 -0
  20. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  21. package/Server/Middleware/OnCallCalendarFeedRateLimit.ts +487 -0
  22. package/Server/Middleware/ProjectAuthorization.ts +6 -1
  23. package/Server/Services/DashboardDomainService.ts +131 -0
  24. package/Server/Services/Index.ts +12 -0
  25. package/Server/Services/LogPipelineProcessorService.ts +88 -0
  26. package/Server/Services/MonitorTemplateService.ts +70 -11
  27. package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +32 -0
  28. package/Server/Services/OnCallDutyPolicyScheduleCalendarFeedService.ts +260 -0
  29. package/Server/Services/OnCallDutyPolicyScheduleLayerService.ts +70 -3
  30. package/Server/Services/OnCallDutyPolicyScheduleLayerUserService.ts +138 -3
  31. package/Server/Services/OnCallDutyPolicyScheduleService.ts +1465 -134
  32. package/Server/Services/OnCallDutyPolicyUserOverrideService.ts +167 -31
  33. package/Server/Services/ProjectOnCallCalendarFeedService.ts +192 -0
  34. package/Server/Services/StatusPageDomainService.ts +163 -3
  35. package/Server/Services/StatusPagePrivateUserSessionService.ts +197 -56
  36. package/Server/Services/TeamMemberService.ts +489 -1
  37. package/Server/Services/TeamService.ts +50 -0
  38. package/Server/Services/UserNotificationSettingService.ts +44 -1
  39. package/Server/Services/UserOnCallCalendarFeedService.ts +159 -0
  40. package/Server/Services/UserOnCallShiftReminderLogService.ts +151 -0
  41. package/Server/Services/UserOnCallShiftReminderService.ts +112 -0
  42. package/Server/Types/Domain.ts +17 -0
  43. package/Server/Utils/APIKey/AccessPermission.ts +24 -0
  44. package/Server/Utils/Greenlock/Greenlock.ts +156 -58
  45. package/Server/Utils/LogPipelineProcessorValidation.ts +112 -0
  46. package/Server/Utils/OnCall/CalendarFeedToken.ts +241 -0
  47. package/Server/Utils/OnCall/OnCallCalendarFeedRenderer.ts +1488 -0
  48. package/Server/Utils/OnCall/OnCallCalendarFeedUrls.ts +230 -0
  49. package/Server/Utils/OnCall/OnCallShiftChangeListeners.ts +227 -0
  50. package/Server/Utils/OnCall/OnCallShiftMaterializer.ts +1127 -0
  51. package/Server/Utils/OnCall/OnCallShiftReminderListener.ts +174 -0
  52. package/Server/Utils/OnCall/OnCallShiftReminderRunner.ts +2584 -0
  53. package/Server/Utils/Response.ts +134 -0
  54. package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.ts +1094 -45
  55. package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.ts +153 -17
  56. package/Server/Utils/StartServer.ts +10 -0
  57. package/Server/Utils/Telemetry/AppMetrics.ts +51 -0
  58. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.ts +23 -11
  59. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.ts +0 -170
  60. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.ts +158 -0
  61. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +23 -11
  62. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +35 -13
  63. package/Tests/App/Dashboard/MonitorTemplateColumnAndFacet.test.tsx +505 -0
  64. package/Tests/App/Dashboard/OnCallCalendarFeedLinks.test.tsx +229 -0
  65. package/Tests/App/Dashboard/OnCallCalendarFeedPlanGate.test.tsx +489 -0
  66. package/Tests/App/Dashboard/OnCallCalendarFeedSideMenus.test.tsx +149 -0
  67. package/Tests/App/Dashboard/OnCallCalendarFeedUtil.test.ts +855 -0
  68. package/Tests/App/Dashboard/OnCallPersonalCalendarFeed.test.tsx +1209 -0
  69. package/Tests/App/Dashboard/OnCallSharedCalendarFeedCard.test.tsx +1019 -0
  70. package/Tests/App/Dashboard/OnCallShiftRemindersAndUpcomingShifts.test.tsx +655 -0
  71. package/Tests/App/Dashboard/UserSettingsSetupChecklistModel.test.ts +73 -0
  72. package/Tests/App/StatusPage/StatusPageLoginCodeBootstrap.test.ts +106 -0
  73. package/Tests/App/StatusPage/StatusPageLoginCodeUtil.test.ts +84 -0
  74. package/Tests/App/StatusPage/StatusPageRedirectSafety.test.ts +119 -0
  75. package/Tests/Models/MonitorTemplateMonitorNameColumn.test.ts +258 -0
  76. package/Tests/Models/OnCallCalendarFeedModels.test.ts +642 -0
  77. package/Tests/Models/OnCallDutyPolicyScheduleCalendarFeed.test.ts +288 -0
  78. package/Tests/Models/OnCallDutyPolicyScheduleShiftConfigVersion.test.ts +88 -0
  79. package/Tests/Models/UserOnCallShiftReminderModels.test.ts +368 -0
  80. package/Tests/Server/API/CustomDomainReissueSslAPI.test.ts +469 -0
  81. package/Tests/Server/API/Helpers.ts +8 -0
  82. package/Tests/Server/API/MicrosoftTeamsBotMessagesEndpoint.test.ts +729 -0
  83. package/Tests/Server/API/MicrosoftTeamsBotTestEndpoint.test.ts +26 -0
  84. package/Tests/Server/API/MicrosoftTeamsLegacyWebhookRemoval.test.ts +402 -0
  85. package/Tests/Server/API/OnCallCalendarAPI.test.ts +4461 -0
  86. package/Tests/Server/EnvironmentConfigOnCallCalendarFeed.test.ts +357 -0
  87. package/Tests/Server/Infrastructure/OnCallCalendarFeedCache.test.ts +1589 -0
  88. package/Tests/Server/Infrastructure/Postgres/AddOnCallCalendarFeedsMigration.test.ts +369 -0
  89. package/Tests/Server/Middleware/OnCallCalendarFeedRateLimit.test.ts +1365 -0
  90. package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +4 -0
  91. package/Tests/Server/Services/AddCertificateReissueRequestedAtToDomainsMigration.test.ts +287 -0
  92. package/Tests/Server/Services/CustomDomainCertificateReissue.test.ts +477 -0
  93. package/Tests/Server/Services/FeedRetentionConsistency.test.ts +70 -0
  94. package/Tests/Server/Services/LogPipelineProcessorSaveValidation.test.ts +249 -0
  95. package/Tests/Server/Services/MonitorTemplateServiceBulkSync.test.ts +549 -0
  96. package/Tests/Server/Services/MonitorTemplateServiceNetworkDeviceSync.test.ts +16 -3
  97. package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +57 -0
  98. package/Tests/Server/Services/OnCallCalendarFeedServices.test.ts +1103 -0
  99. package/Tests/Server/Services/OnCallDutyPolicyScheduleResolver.test.ts +1037 -0
  100. package/Tests/Server/Services/OnCallShiftConfigPropagation.test.ts +1270 -0
  101. package/Tests/Server/Services/SecurityEventLastErrorColumnWidth.test.ts +151 -15
  102. package/Tests/Server/Services/StatusPagePrivateUserSessionService.test.ts +587 -0
  103. package/Tests/Server/Services/TeamDeleteMemberCleanup.test.ts +156 -0
  104. package/Tests/Server/Services/TeamMemberOnCallCleanup.test.ts +755 -0
  105. package/Tests/Server/Services/UserNotificationSettingShiftReminderDefaults.test.ts +267 -0
  106. package/Tests/Server/Services/UserOnCallShiftReminderServices.test.ts +583 -0
  107. package/Tests/Server/Types/Database/Permissions/ApiKeyFileAccess.test.ts +286 -0
  108. package/Tests/Server/Types/Database/Permissions/MonitorTemplateColumnAccess.test.ts +328 -0
  109. package/Tests/Server/Types/Database/Permissions/OnCallCalendarFeedPermissions.test.ts +254 -0
  110. package/Tests/Server/Types/Database/QueryHelperFindWithSameTextAndIsNull.test.ts +83 -0
  111. package/Tests/Server/Utils/AI/SRE/ConfidenceSignal.test.ts +377 -0
  112. package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +16 -1
  113. package/Tests/Server/Utils/Greenlock/CertificateRenewalSchedule.test.ts +250 -0
  114. package/Tests/Server/Utils/LogPipelineProcessorValidation.test.ts +198 -0
  115. package/Tests/Server/Utils/OnCall/CalendarFeedToken.test.ts +467 -0
  116. package/Tests/Server/Utils/OnCall/OnCallCalendarFeedRenderer.test.ts +2142 -0
  117. package/Tests/Server/Utils/OnCall/OnCallCalendarFeedUrls.test.ts +278 -0
  118. package/Tests/Server/Utils/OnCall/OnCallResolverTestHarness.ts +440 -0
  119. package/Tests/Server/Utils/OnCall/OnCallShiftChangeListeners.test.ts +314 -0
  120. package/Tests/Server/Utils/OnCall/OnCallShiftMaterializer.test.ts +1502 -0
  121. package/Tests/Server/Utils/OnCall/OnCallShiftReminderListener.test.ts +491 -0
  122. package/Tests/Server/Utils/OnCall/OnCallShiftReminderRunner.test.ts +3088 -0
  123. package/Tests/Server/Utils/OnCall/OnCallShiftReminderTestHarness.ts +910 -0
  124. package/Tests/Server/Utils/ResponseSendCalendarResponse.test.ts +561 -0
  125. package/Tests/Server/Utils/SecurityEvent/ConnectorErrorMessage.test.ts +48 -9
  126. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsAuth.test.ts +726 -0
  127. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsClient.test.ts +74 -11
  128. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsErrorHandling.test.ts +1107 -0
  129. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPoller.test.ts +20 -3
  130. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerFailureTaxonomy.test.ts +51 -11
  131. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerHardening.test.ts +1246 -0
  132. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsRequestContract.test.ts +961 -0
  133. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsResponseParsing.test.ts +653 -0
  134. package/Tests/Server/Utils/StartServerEncryptionSecretWarning.test.ts +64 -0
  135. package/Tests/Server/Utils/Telemetry/AppMetrics.test.ts +29 -0
  136. package/Tests/Server/Utils/Workspace/MicrosoftTeamsActionAuthorization.test.ts +880 -0
  137. package/Tests/Types/API/HTTPResponse.test.ts +175 -0
  138. package/Tests/Types/API/Route.test.ts +36 -4
  139. package/Tests/Types/Calendar/ICalendar.test.ts +488 -0
  140. package/Tests/Types/Database/DatabaseProperty.test.ts +171 -0
  141. package/Tests/Types/DateLocalShortDateTimeString.test.ts +518 -0
  142. package/Tests/Types/DateLocalTimeString.test.ts +259 -0
  143. package/Tests/Types/DateUserPrefers12HourFormat.test.ts +316 -0
  144. package/Tests/Types/NotificationSetting/NotificationSettingEventType.test.ts +53 -0
  145. package/Tests/Types/OnCallDutyPolicy/CalendarFeedTestFixtures.ts +310 -0
  146. package/Tests/Types/OnCallDutyPolicy/CalendarFeedWindow.test.ts +205 -0
  147. package/Tests/Types/OnCallDutyPolicy/LayerUtilLayerMetaAndIterationCap.test.ts +458 -0
  148. package/Tests/Types/OnCallDutyPolicy/MaterializedShift.test.ts +197 -0
  149. package/Tests/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.test.ts +1986 -0
  150. package/Tests/Types/OnCallDutyPolicy/ScheduleShiftUtilGroupKey.test.ts +678 -0
  151. package/Tests/Types/OnCallDutyPolicy/ShiftSeamUtil.test.ts +417 -0
  152. package/Tests/UI/Components/LogsHistogram.test.tsx +41 -2
  153. package/Tests/UI/Components/LogsViewerEmptyStateTimeRange.test.tsx +212 -0
  154. package/Tests/UI/Components/TelemetryChartClockFormat.test.tsx +359 -0
  155. package/Tests/UI/Components/TimeRangePickerClockFormat.test.tsx +422 -0
  156. package/Tests/UI/Components/TimeRangePickerDropdown.test.tsx +146 -8
  157. package/Tests/UI/MicrosoftTeams/MicrosoftTeamsMessagingEndpointGuidance.test.tsx +520 -0
  158. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +10 -0
  159. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +2 -0
  160. package/Tests/UI/Utils/Navigation.test.ts +82 -0
  161. package/Tests/Utils/Array.test.ts +100 -0
  162. package/Tests/Utils/CertificateReissue.test.ts +225 -0
  163. package/Tests/Utils/Grok/Grok.test.ts +492 -0
  164. package/Tests/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.test.ts +181 -13
  165. package/Types/API/Route.ts +20 -5
  166. package/Types/Calendar/ICalendar.ts +442 -0
  167. package/Types/Date.ts +143 -13
  168. package/Types/Email/EmailTemplateType.ts +2 -0
  169. package/Types/NotificationSetting/NotificationSettingEventType.ts +4 -0
  170. package/Types/OnCallDutyPolicy/CalendarFeedWindow.ts +140 -0
  171. package/Types/OnCallDutyPolicy/Layer.ts +190 -35
  172. package/Types/OnCallDutyPolicy/MaterializedShift.ts +274 -0
  173. package/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.ts +1415 -0
  174. package/Types/OnCallDutyPolicy/ScheduleShiftUtil.ts +159 -4
  175. package/Types/OnCallDutyPolicy/ShiftSeamUtil.ts +106 -0
  176. package/Types/Rum/SessionReplay.ts +62 -1
  177. package/UI/Components/Date/TimeRangePickerDropdown.tsx +9 -5
  178. package/UI/Components/LogsViewer/LogsViewer.tsx +12 -7
  179. package/UI/Components/LogsViewer/components/HistogramTooltip.tsx +18 -17
  180. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +21 -28
  181. package/UI/Components/LogsViewer/components/LogsHistogram.tsx +2 -4
  182. package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +2 -4
  183. package/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.tsx +17 -16
  184. package/UI/Utils/Navigation.ts +42 -0
  185. package/Utils/Array.ts +37 -0
  186. package/Utils/CertificateReissue.ts +128 -0
  187. package/Utils/Grok/Grok.ts +461 -0
  188. package/Utils/Grok/GrokPatterns.ts +118 -0
  189. package/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.ts +58 -6
  190. package/build/dist/Models/DatabaseModels/DashboardDomain.js +44 -0
  191. package/build/dist/Models/DatabaseModels/DashboardDomain.js.map +1 -1
  192. package/build/dist/Models/DatabaseModels/Index.js +11 -0
  193. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  194. package/build/dist/Models/DatabaseModels/MonitorTemplate.js +17 -4
  195. package/build/dist/Models/DatabaseModels/MonitorTemplate.js.map +1 -1
  196. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +44 -0
  197. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
  198. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.js +699 -0
  199. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.js.map +1 -0
  200. package/build/dist/Models/DatabaseModels/ProjectOnCallCalendarFeed.js +620 -0
  201. package/build/dist/Models/DatabaseModels/ProjectOnCallCalendarFeed.js.map +1 -0
  202. package/build/dist/Models/DatabaseModels/StatusPageDomain.js +44 -0
  203. package/build/dist/Models/DatabaseModels/StatusPageDomain.js.map +1 -1
  204. package/build/dist/Models/DatabaseModels/UserOnCallCalendarFeed.js +585 -0
  205. package/build/dist/Models/DatabaseModels/UserOnCallCalendarFeed.js.map +1 -0
  206. package/build/dist/Models/DatabaseModels/UserOnCallShiftReminder.js +247 -0
  207. package/build/dist/Models/DatabaseModels/UserOnCallShiftReminder.js.map +1 -0
  208. package/build/dist/Models/DatabaseModels/UserOnCallShiftReminderLog.js +355 -0
  209. package/build/dist/Models/DatabaseModels/UserOnCallShiftReminderLog.js.map +1 -0
  210. package/build/dist/Server/API/DashboardDomainAPI.js +43 -1
  211. package/build/dist/Server/API/DashboardDomainAPI.js.map +1 -1
  212. package/build/dist/Server/API/MicrosoftTeamsAPI.js +74 -118
  213. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  214. package/build/dist/Server/API/OnCallCalendarAPI.js +1371 -0
  215. package/build/dist/Server/API/OnCallCalendarAPI.js.map +1 -0
  216. package/build/dist/Server/API/StatusPageDomainAPI.js +43 -1
  217. package/build/dist/Server/API/StatusPageDomainAPI.js.map +1 -1
  218. package/build/dist/Server/EnvironmentConfig.js +102 -0
  219. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  220. package/build/dist/Server/Infrastructure/OnCallCalendarFeedCache.js +663 -0
  221. package/build/dist/Server/Infrastructure/OnCallCalendarFeedCache.js.map +1 -0
  222. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.js +34 -0
  223. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.js.map +1 -0
  224. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.js +28 -0
  225. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.js.map +1 -0
  226. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790400000000-AddOnCallCalendarFeeds.js +103 -0
  227. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790400000000-AddOnCallCalendarFeeds.js.map +1 -0
  228. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  229. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  230. package/build/dist/Server/Middleware/OnCallCalendarFeedRateLimit.js +342 -0
  231. package/build/dist/Server/Middleware/OnCallCalendarFeedRateLimit.js.map +1 -0
  232. package/build/dist/Server/Middleware/ProjectAuthorization.js +6 -1
  233. package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
  234. package/build/dist/Server/Services/DashboardDomainService.js +104 -0
  235. package/build/dist/Server/Services/DashboardDomainService.js.map +1 -1
  236. package/build/dist/Server/Services/Index.js +12 -0
  237. package/build/dist/Server/Services/Index.js.map +1 -1
  238. package/build/dist/Server/Services/LogPipelineProcessorService.js +65 -0
  239. package/build/dist/Server/Services/LogPipelineProcessorService.js.map +1 -1
  240. package/build/dist/Server/Services/MonitorTemplateService.js +57 -11
  241. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  242. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +20 -0
  243. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
  244. package/build/dist/Server/Services/OnCallDutyPolicyScheduleCalendarFeedService.js +219 -0
  245. package/build/dist/Server/Services/OnCallDutyPolicyScheduleCalendarFeedService.js.map +1 -0
  246. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js +47 -3
  247. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js.map +1 -1
  248. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js +112 -3
  249. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js.map +1 -1
  250. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +955 -9
  251. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
  252. package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js +93 -6
  253. package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js.map +1 -1
  254. package/build/dist/Server/Services/ProjectOnCallCalendarFeedService.js +157 -0
  255. package/build/dist/Server/Services/ProjectOnCallCalendarFeedService.js.map +1 -0
  256. package/build/dist/Server/Services/StatusPageDomainService.js +132 -3
  257. package/build/dist/Server/Services/StatusPageDomainService.js.map +1 -1
  258. package/build/dist/Server/Services/StatusPagePrivateUserSessionService.js +112 -32
  259. package/build/dist/Server/Services/StatusPagePrivateUserSessionService.js.map +1 -1
  260. package/build/dist/Server/Services/TeamMemberService.js +396 -1
  261. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  262. package/build/dist/Server/Services/TeamService.js +43 -0
  263. package/build/dist/Server/Services/TeamService.js.map +1 -1
  264. package/build/dist/Server/Services/UserNotificationSettingService.js +26 -1
  265. package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
  266. package/build/dist/Server/Services/UserOnCallCalendarFeedService.js +140 -0
  267. package/build/dist/Server/Services/UserOnCallCalendarFeedService.js.map +1 -0
  268. package/build/dist/Server/Services/UserOnCallShiftReminderLogService.js +116 -0
  269. package/build/dist/Server/Services/UserOnCallShiftReminderLogService.js.map +1 -0
  270. package/build/dist/Server/Services/UserOnCallShiftReminderService.js +92 -0
  271. package/build/dist/Server/Services/UserOnCallShiftReminderService.js.map +1 -0
  272. package/build/dist/Server/Types/Domain.js +16 -0
  273. package/build/dist/Server/Types/Domain.js.map +1 -1
  274. package/build/dist/Server/Utils/APIKey/AccessPermission.js +24 -0
  275. package/build/dist/Server/Utils/APIKey/AccessPermission.js.map +1 -1
  276. package/build/dist/Server/Utils/Greenlock/Greenlock.js +110 -35
  277. package/build/dist/Server/Utils/Greenlock/Greenlock.js.map +1 -1
  278. package/build/dist/Server/Utils/LogPipelineProcessorValidation.js +77 -0
  279. package/build/dist/Server/Utils/LogPipelineProcessorValidation.js.map +1 -0
  280. package/build/dist/Server/Utils/OnCall/CalendarFeedToken.js +161 -0
  281. package/build/dist/Server/Utils/OnCall/CalendarFeedToken.js.map +1 -0
  282. package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedRenderer.js +983 -0
  283. package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedRenderer.js.map +1 -0
  284. package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedUrls.js +155 -0
  285. package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedUrls.js.map +1 -0
  286. package/build/dist/Server/Utils/OnCall/OnCallShiftChangeListeners.js +145 -0
  287. package/build/dist/Server/Utils/OnCall/OnCallShiftChangeListeners.js.map +1 -0
  288. package/build/dist/Server/Utils/OnCall/OnCallShiftMaterializer.js +752 -0
  289. package/build/dist/Server/Utils/OnCall/OnCallShiftMaterializer.js.map +1 -0
  290. package/build/dist/Server/Utils/OnCall/OnCallShiftReminderListener.js +92 -0
  291. package/build/dist/Server/Utils/OnCall/OnCallShiftReminderListener.js.map +1 -0
  292. package/build/dist/Server/Utils/OnCall/OnCallShiftReminderRunner.js +1786 -0
  293. package/build/dist/Server/Utils/OnCall/OnCallShiftReminderRunner.js.map +1 -0
  294. package/build/dist/Server/Utils/Response.js +97 -0
  295. package/build/dist/Server/Utils/Response.js.map +1 -1
  296. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js +777 -36
  297. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js.map +1 -1
  298. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js +108 -17
  299. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js.map +1 -1
  300. package/build/dist/Server/Utils/StartServer.js +9 -1
  301. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  302. package/build/dist/Server/Utils/Telemetry/AppMetrics.js +41 -0
  303. package/build/dist/Server/Utils/Telemetry/AppMetrics.js.map +1 -1
  304. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js +17 -6
  305. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js.map +1 -1
  306. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js +0 -159
  307. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js.map +1 -1
  308. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.js +128 -0
  309. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.js.map +1 -0
  310. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +17 -6
  311. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
  312. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +25 -13
  313. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  314. package/build/dist/Types/API/Route.js +16 -5
  315. package/build/dist/Types/API/Route.js.map +1 -1
  316. package/build/dist/Types/Calendar/ICalendar.js +290 -0
  317. package/build/dist/Types/Calendar/ICalendar.js.map +1 -0
  318. package/build/dist/Types/Date.js +115 -13
  319. package/build/dist/Types/Date.js.map +1 -1
  320. package/build/dist/Types/Email/EmailTemplateType.js +2 -0
  321. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  322. package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js +3 -0
  323. package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js.map +1 -1
  324. package/build/dist/Types/OnCallDutyPolicy/CalendarFeedWindow.js +97 -0
  325. package/build/dist/Types/OnCallDutyPolicy/CalendarFeedWindow.js.map +1 -0
  326. package/build/dist/Types/OnCallDutyPolicy/Layer.js +113 -19
  327. package/build/dist/Types/OnCallDutyPolicy/Layer.js.map +1 -1
  328. package/build/dist/Types/OnCallDutyPolicy/MaterializedShift.js +134 -0
  329. package/build/dist/Types/OnCallDutyPolicy/MaterializedShift.js.map +1 -0
  330. package/build/dist/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.js +800 -0
  331. package/build/dist/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.js.map +1 -0
  332. package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js +97 -3
  333. package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js.map +1 -1
  334. package/build/dist/Types/OnCallDutyPolicy/ShiftSeamUtil.js +77 -0
  335. package/build/dist/Types/OnCallDutyPolicy/ShiftSeamUtil.js.map +1 -0
  336. package/build/dist/Types/Rum/SessionReplay.js +38 -0
  337. package/build/dist/Types/Rum/SessionReplay.js.map +1 -1
  338. package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js +9 -5
  339. package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js.map +1 -1
  340. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +12 -7
  341. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  342. package/build/dist/UI/Components/LogsViewer/components/HistogramTooltip.js +15 -14
  343. package/build/dist/UI/Components/LogsViewer/components/HistogramTooltip.js.map +1 -1
  344. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +17 -22
  345. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  346. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +2 -4
  347. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
  348. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +2 -4
  349. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
  350. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js +15 -14
  351. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js.map +1 -1
  352. package/build/dist/UI/Utils/Navigation.js +30 -0
  353. package/build/dist/UI/Utils/Navigation.js.map +1 -1
  354. package/build/dist/Utils/Array.js +25 -0
  355. package/build/dist/Utils/Array.js.map +1 -1
  356. package/build/dist/Utils/CertificateReissue.js +97 -0
  357. package/build/dist/Utils/CertificateReissue.js.map +1 -0
  358. package/build/dist/Utils/Grok/Grok.js +324 -0
  359. package/build/dist/Utils/Grok/Grok.js.map +1 -0
  360. package/build/dist/Utils/Grok/GrokPatterns.js +112 -0
  361. package/build/dist/Utils/Grok/GrokPatterns.js.map +1 -0
  362. package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js +63 -3
  363. package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js.map +1 -1
  364. package/package.json +1 -1
  365. package/UI/Utils/JsonWebToken.ts +0 -14
  366. package/build/dist/UI/Utils/JsonWebToken.js +0 -10
  367. package/build/dist/UI/Utils/JsonWebToken.js.map +0 -1
@@ -0,0 +1,198 @@
1
+ import { validateLogPipelineProcessor } from "../../../Server/Utils/LogPipelineProcessorValidation";
2
+ import BadDataException from "../../../Types/Exception/BadDataException";
3
+ import { JSONObject } from "../../../Types/JSON";
4
+ import LogPipelineProcessorType from "../../../Types/Log/LogPipelineProcessorType";
5
+ import { describe, expect, it } from "@jest/globals";
6
+
7
+ /*
8
+ * Contract under test — the save-time gate on log pipeline processors.
9
+ *
10
+ * A processor that cannot run is invisible: it sits in the pipeline list
11
+ * looking configured while every log flows past it untouched. That is
12
+ * exactly the shape of OneUptime/oneuptime#2515, where GrokParser was a
13
+ * valid processor type with no implementation behind it.
14
+ *
15
+ * The grok pattern is where a typo is easy and silent, so it is compiled
16
+ * here — once per human edit, in front of the only person who can fix
17
+ * it. Everything else is left alone: this gate exists to catch what
18
+ * would otherwise fail quietly, not to re-validate the whole form.
19
+ */
20
+
21
+ function grokProcessor(configuration: JSONObject | string): {
22
+ processorType: string;
23
+ configuration: JSONObject | string;
24
+ } {
25
+ return {
26
+ processorType: LogPipelineProcessorType.GrokParser,
27
+ configuration: configuration,
28
+ };
29
+ }
30
+
31
+ describe("log pipeline processor validation — grok patterns", () => {
32
+ it("accepts a valid pattern", () => {
33
+ expect(() => {
34
+ return validateLogPipelineProcessor(
35
+ grokProcessor({
36
+ source: "body",
37
+ pattern: "%{IPV4:client_ip} %{WORD:verb} %{NUMBER:status:int}",
38
+ }),
39
+ );
40
+ }).not.toThrow();
41
+ });
42
+
43
+ it("accepts a configuration persisted as a JSON string", () => {
44
+ expect(() => {
45
+ return validateLogPipelineProcessor(
46
+ grokProcessor(
47
+ JSON.stringify({ source: "body", pattern: "%{WORD:verb}" }),
48
+ ),
49
+ );
50
+ }).not.toThrow();
51
+ });
52
+
53
+ it("rejects a configuration string that is not JSON", () => {
54
+ expect(() => {
55
+ return validateLogPipelineProcessor(grokProcessor("not json at all"));
56
+ }).toThrow(BadDataException);
57
+ });
58
+
59
+ it("rejects a missing configuration", () => {
60
+ expect(() => {
61
+ return validateLogPipelineProcessor({
62
+ processorType: LogPipelineProcessorType.GrokParser,
63
+ configuration: null,
64
+ });
65
+ }).toThrow(BadDataException);
66
+ });
67
+
68
+ it("rejects a missing or blank pattern", () => {
69
+ for (const pattern of [undefined, "", " "]) {
70
+ expect(() => {
71
+ return validateLogPipelineProcessor(
72
+ grokProcessor({ source: "body", pattern: pattern as string }),
73
+ );
74
+ }).toThrow(BadDataException);
75
+ }
76
+ });
77
+
78
+ it("rejects an unknown pattern name, naming it in the error", () => {
79
+ expect(() => {
80
+ return validateLogPipelineProcessor(
81
+ grokProcessor({ source: "body", pattern: "%{NOSUCHTHING:x}" }),
82
+ );
83
+ }).toThrow('Unknown grok pattern "%{NOSUCHTHING}"');
84
+ });
85
+
86
+ it("rejects a pattern whose regex does not parse", () => {
87
+ expect(() => {
88
+ return validateLogPipelineProcessor(
89
+ grokProcessor({ source: "body", pattern: "%{WORD:verb} ([unclosed" }),
90
+ );
91
+ }).toThrow(BadDataException);
92
+ });
93
+
94
+ it("rejects a field name that could not become an attribute key", () => {
95
+ expect(() => {
96
+ return validateLogPipelineProcessor(
97
+ grokProcessor({ source: "body", pattern: "%{WORD:client ip}" }),
98
+ );
99
+ }).toThrow(BadDataException);
100
+ });
101
+
102
+ it("rejects an unknown capture type", () => {
103
+ expect(() => {
104
+ return validateLogPipelineProcessor(
105
+ grokProcessor({ source: "body", pattern: "%{NUMBER:status:decimal}" }),
106
+ );
107
+ }).toThrow(BadDataException);
108
+ });
109
+ });
110
+
111
+ describe("log pipeline processor validation — target prefix", () => {
112
+ it("accepts prefixes that read like attribute-key namespaces", () => {
113
+ for (const targetPrefix of ["http", "http.", "http_", "_private", "a-b"]) {
114
+ expect(() => {
115
+ return validateLogPipelineProcessor(
116
+ grokProcessor({
117
+ source: "body",
118
+ pattern: "%{WORD:verb}",
119
+ targetPrefix,
120
+ }),
121
+ );
122
+ }).not.toThrow();
123
+ }
124
+ });
125
+
126
+ it("accepts an absent or blank prefix", () => {
127
+ for (const targetPrefix of [undefined, "", " "]) {
128
+ expect(() => {
129
+ return validateLogPipelineProcessor(
130
+ grokProcessor({
131
+ source: "body",
132
+ pattern: "%{WORD:verb}",
133
+ targetPrefix: targetPrefix as string,
134
+ }),
135
+ );
136
+ }).not.toThrow();
137
+ }
138
+ });
139
+
140
+ it("rejects a prefix with characters an attribute key should not carry", () => {
141
+ for (const targetPrefix of ["my prefix", "1http", "http!", "{x}"]) {
142
+ expect(() => {
143
+ return validateLogPipelineProcessor(
144
+ grokProcessor({
145
+ source: "body",
146
+ pattern: "%{WORD:verb}",
147
+ targetPrefix,
148
+ }),
149
+ );
150
+ }).toThrow(BadDataException);
151
+ }
152
+ });
153
+
154
+ it("rejects a non-string prefix or source", () => {
155
+ expect(() => {
156
+ return validateLogPipelineProcessor(
157
+ grokProcessor({
158
+ source: "body",
159
+ pattern: "%{WORD:verb}",
160
+ targetPrefix: 12 as unknown as string,
161
+ } as unknown as JSONObject),
162
+ );
163
+ }).toThrow(BadDataException);
164
+
165
+ expect(() => {
166
+ return validateLogPipelineProcessor(
167
+ grokProcessor({
168
+ source: 12 as unknown as string,
169
+ pattern: "%{WORD:verb}",
170
+ } as unknown as JSONObject),
171
+ );
172
+ }).toThrow(BadDataException);
173
+ });
174
+ });
175
+
176
+ describe("log pipeline processor validation — other processor types", () => {
177
+ /*
178
+ * The other three processor types were shipped without a save-time
179
+ * gate and are not silently broken the way grok was. Adding one for
180
+ * them is a separate decision; this pins that it did not happen by
181
+ * accident here.
182
+ */
183
+ it("leaves non-grok processors alone", () => {
184
+ for (const processorType of [
185
+ LogPipelineProcessorType.AttributeRemapper,
186
+ LogPipelineProcessorType.SeverityRemapper,
187
+ LogPipelineProcessorType.CategoryProcessor,
188
+ "SomethingElse",
189
+ ]) {
190
+ expect(() => {
191
+ return validateLogPipelineProcessor({
192
+ processorType: processorType,
193
+ configuration: { anything: "goes" },
194
+ });
195
+ }).not.toThrow();
196
+ }
197
+ });
198
+ });
@@ -0,0 +1,467 @@
1
+ import CalendarFeedToken, {
2
+ CALENDAR_FEED_TOKEN_HINT_LENGTH,
3
+ CALENDAR_FEED_TOKEN_LENGTH,
4
+ CALENDAR_FEED_TOKEN_REGEX,
5
+ CalendarFeedRotation,
6
+ CalendarFeedRotationUpdateData,
7
+ CalendarFeedTokenColumns,
8
+ MintedCalendarFeedToken,
9
+ } from "../../../../Server/Utils/OnCall/CalendarFeedToken";
10
+ import { PREVIOUS_TOKEN_GRACE_DAYS } from "../../../../Types/OnCallDutyPolicy/CalendarFeedWindow";
11
+ import crypto from "crypto";
12
+ import { describe, expect, test } from "@jest/globals";
13
+
14
+ /*
15
+ * The token is the only secret in the whole calendar-feed feature: it is the
16
+ * feed URL. These tests pin its shape (what the public route's regex guard
17
+ * accepts), its entropy (what makes guessing hopeless), its hash (what the
18
+ * database indexes) and its hint (what the UI may show).
19
+ */
20
+
21
+ const SAMPLE_COUNT: number = 200;
22
+
23
+ describe("CalendarFeedToken.mint", () => {
24
+ test("mints a 43-character base64url token", () => {
25
+ for (let i: number = 0; i < SAMPLE_COUNT; i++) {
26
+ const token: string = CalendarFeedToken.mint();
27
+
28
+ expect(token).toHaveLength(CALENDAR_FEED_TOKEN_LENGTH);
29
+ expect(token).toHaveLength(43);
30
+ expect(token).toMatch(/^[A-Za-z0-9_-]+$/);
31
+ }
32
+ });
33
+
34
+ test("never carries base64 padding or the standard-alphabet symbols", () => {
35
+ for (let i: number = 0; i < SAMPLE_COUNT; i++) {
36
+ const token: string = CalendarFeedToken.mint();
37
+
38
+ expect(token).not.toContain("=");
39
+ expect(token).not.toContain("+");
40
+ expect(token).not.toContain("/");
41
+ }
42
+ });
43
+
44
+ test("decodes back to exactly 32 random bytes", () => {
45
+ const token: string = CalendarFeedToken.mint();
46
+
47
+ expect(Buffer.from(token, "base64url")).toHaveLength(32);
48
+ });
49
+
50
+ test("is unique across many mints", () => {
51
+ const tokens: Set<string> = new Set<string>();
52
+
53
+ for (let i: number = 0; i < SAMPLE_COUNT; i++) {
54
+ tokens.add(CalendarFeedToken.mint());
55
+ }
56
+
57
+ expect(tokens.size).toBe(SAMPLE_COUNT);
58
+ });
59
+
60
+ test("uses both halves of the base64url alphabet (not hex)", () => {
61
+ /*
62
+ * A regression guard against somebody "simplifying" the mint to hex: hex
63
+ * would still pass a length check if the byte count were adjusted, but
64
+ * it would never contain an uppercase letter beyond F, nor '-' or '_'.
65
+ */
66
+ const joined: string = Array.from({ length: SAMPLE_COUNT }, () => {
67
+ return CalendarFeedToken.mint();
68
+ }).join("");
69
+
70
+ expect(joined).toMatch(/[G-Zg-z]/);
71
+ expect(joined).toMatch(/[-_]/);
72
+ });
73
+ });
74
+
75
+ describe("CALENDAR_FEED_TOKEN_REGEX", () => {
76
+ test("is the documented shape guard", () => {
77
+ expect(CALENDAR_FEED_TOKEN_REGEX.source).toBe("^[A-Za-z0-9_-]{43}$");
78
+ expect(CALENDAR_FEED_TOKEN_REGEX.flags).toBe("");
79
+ });
80
+
81
+ test("accepts every minted token", () => {
82
+ for (let i: number = 0; i < SAMPLE_COUNT; i++) {
83
+ expect(CALENDAR_FEED_TOKEN_REGEX.test(CalendarFeedToken.mint())).toBe(
84
+ true,
85
+ );
86
+ }
87
+ });
88
+
89
+ test("rejects the wrong length, padding, and foreign characters", () => {
90
+ const good: string = CalendarFeedToken.mint();
91
+
92
+ expect(CALENDAR_FEED_TOKEN_REGEX.test(good.slice(0, 42))).toBe(false);
93
+ expect(CALENDAR_FEED_TOKEN_REGEX.test(good + "a")).toBe(false);
94
+ expect(CALENDAR_FEED_TOKEN_REGEX.test(good.slice(0, 42) + "=")).toBe(false);
95
+ expect(CALENDAR_FEED_TOKEN_REGEX.test(good.slice(0, 42) + "+")).toBe(false);
96
+ expect(CALENDAR_FEED_TOKEN_REGEX.test(good.slice(0, 42) + "/")).toBe(false);
97
+ expect(CALENDAR_FEED_TOKEN_REGEX.test(good.slice(0, 42) + " ")).toBe(false);
98
+ expect(CALENDAR_FEED_TOKEN_REGEX.test(good.slice(0, 42) + "\n")).toBe(
99
+ false,
100
+ );
101
+ expect(CALENDAR_FEED_TOKEN_REGEX.test("")).toBe(false);
102
+ });
103
+
104
+ test("rejects a UUID and a 64-hex digest (other token shapes in the codebase)", () => {
105
+ expect(
106
+ CALENDAR_FEED_TOKEN_REGEX.test("11111111-1111-4111-8111-111111111111"),
107
+ ).toBe(false);
108
+ expect(CALENDAR_FEED_TOKEN_REGEX.test("a".repeat(64))).toBe(false);
109
+ });
110
+
111
+ test("is anchored: a valid token embedded in junk does not match", () => {
112
+ const good: string = CalendarFeedToken.mint();
113
+
114
+ expect(CALENDAR_FEED_TOKEN_REGEX.test(`x${good}`)).toBe(false);
115
+ expect(CALENDAR_FEED_TOKEN_REGEX.test(`${good}x`)).toBe(false);
116
+ expect(CALENDAR_FEED_TOKEN_REGEX.test(`${good}\n${good}`)).toBe(false);
117
+ });
118
+ });
119
+
120
+ describe("CalendarFeedToken.isValidShape", () => {
121
+ test("agrees with the regex and refuses non-strings", () => {
122
+ const good: string = CalendarFeedToken.mint();
123
+
124
+ expect(CalendarFeedToken.isValidShape(good)).toBe(true);
125
+ expect(CalendarFeedToken.isValidShape(good.slice(1))).toBe(false);
126
+ expect(CalendarFeedToken.isValidShape(undefined)).toBe(false);
127
+ expect(CalendarFeedToken.isValidShape(null)).toBe(false);
128
+ expect(CalendarFeedToken.isValidShape(42)).toBe(false);
129
+ expect(CalendarFeedToken.isValidShape([good])).toBe(false);
130
+ expect(CalendarFeedToken.isValidShape({ token: good })).toBe(false);
131
+ });
132
+ });
133
+
134
+ describe("CalendarFeedToken.hash", () => {
135
+ test("is the unkeyed SHA-256 hex digest of the token", () => {
136
+ const token: string = CalendarFeedToken.mint();
137
+
138
+ expect(CalendarFeedToken.hash(token)).toBe(
139
+ crypto.createHash("sha256").update(token, "utf8").digest("hex"),
140
+ );
141
+ });
142
+
143
+ test("is 64 lowercase hex characters", () => {
144
+ for (let i: number = 0; i < 20; i++) {
145
+ expect(CalendarFeedToken.hash(CalendarFeedToken.mint())).toMatch(
146
+ /^[0-9a-f]{64}$/,
147
+ );
148
+ }
149
+ });
150
+
151
+ test("matches a known SHA-256 test vector", () => {
152
+ // sha256("abc") from FIPS 180-4.
153
+ expect(CalendarFeedToken.hash("abc")).toBe(
154
+ "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
155
+ );
156
+ // sha256("") — the empty message.
157
+ expect(CalendarFeedToken.hash("")).toBe(
158
+ "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
159
+ );
160
+ });
161
+
162
+ test("is deterministic and differs between tokens", () => {
163
+ const a: string = CalendarFeedToken.mint();
164
+ const b: string = CalendarFeedToken.mint();
165
+
166
+ expect(CalendarFeedToken.hash(a)).toBe(CalendarFeedToken.hash(a));
167
+ expect(CalendarFeedToken.hash(a)).not.toBe(CalendarFeedToken.hash(b));
168
+ });
169
+
170
+ test("does not depend on any instance secret", () => {
171
+ /*
172
+ * The token is looked up by hash on the public route; if the hash were
173
+ * keyed by ENCRYPTION_SECRET, rotating that secret would orphan every
174
+ * subscribed calendar. Pin that the digest is a function of the token
175
+ * alone by comparing with the plain library call under a changed env.
176
+ */
177
+ const token: string = CalendarFeedToken.mint();
178
+ const before: string = CalendarFeedToken.hash(token);
179
+
180
+ const previous: string | undefined = process.env["ENCRYPTION_SECRET"];
181
+ process.env["ENCRYPTION_SECRET"] = "a-completely-different-secret";
182
+
183
+ try {
184
+ expect(CalendarFeedToken.hash(token)).toBe(before);
185
+ } finally {
186
+ if (previous === undefined) {
187
+ delete process.env["ENCRYPTION_SECRET"];
188
+ } else {
189
+ process.env["ENCRYPTION_SECRET"] = previous;
190
+ }
191
+ }
192
+ });
193
+
194
+ test("fits the ShortText column it is stored in", () => {
195
+ // ColumnLength.ShortText is 100; a hex SHA-256 is 64.
196
+ expect(CalendarFeedToken.hash(CalendarFeedToken.mint()).length).toBe(64);
197
+ expect(64).toBeLessThanOrEqual(100);
198
+ });
199
+ });
200
+
201
+ describe("CalendarFeedToken.hint", () => {
202
+ test("is the last four characters of the token", () => {
203
+ const token: string = CalendarFeedToken.mint();
204
+
205
+ expect(CalendarFeedToken.hint(token)).toBe(token.slice(-4));
206
+ expect(CalendarFeedToken.hint(token)).toHaveLength(
207
+ CALENDAR_FEED_TOKEN_HINT_LENGTH,
208
+ );
209
+ expect(token.endsWith(CalendarFeedToken.hint(token))).toBe(true);
210
+ });
211
+
212
+ test("reveals nothing but the tail", () => {
213
+ const token: string = CalendarFeedToken.mint();
214
+ const hint: string = CalendarFeedToken.hint(token);
215
+
216
+ // 43 - 4 = 39 characters remain unknown from the hint alone.
217
+ expect(token.length - hint.length).toBe(39);
218
+ expect(token.startsWith(hint)).toBe(false);
219
+ });
220
+
221
+ test("copes with short inputs without throwing", () => {
222
+ expect(CalendarFeedToken.hint("ab")).toBe("ab");
223
+ expect(CalendarFeedToken.hint("")).toBe("");
224
+ });
225
+ });
226
+
227
+ describe("CalendarFeedToken.mintSet", () => {
228
+ test("returns a consistent token/hash/hint triple", () => {
229
+ const set: MintedCalendarFeedToken = CalendarFeedToken.mintSet();
230
+
231
+ expect(CalendarFeedToken.isValidShape(set.token)).toBe(true);
232
+ expect(set.tokenHash).toBe(CalendarFeedToken.hash(set.token));
233
+ expect(set.tokenHint).toBe(CalendarFeedToken.hint(set.token));
234
+ expect(Object.keys(set).sort()).toEqual([
235
+ "token",
236
+ "tokenHash",
237
+ "tokenHint",
238
+ ]);
239
+ });
240
+
241
+ test("mints a different token every call", () => {
242
+ const a: MintedCalendarFeedToken = CalendarFeedToken.mintSet();
243
+ const b: MintedCalendarFeedToken = CalendarFeedToken.mintSet();
244
+
245
+ expect(a.token).not.toBe(b.token);
246
+ expect(a.tokenHash).not.toBe(b.tokenHash);
247
+ });
248
+ });
249
+
250
+ describe("CalendarFeedToken.isHashEqual", () => {
251
+ test("is true for equal digests and false otherwise", () => {
252
+ const token: string = CalendarFeedToken.mint();
253
+ const digest: string = CalendarFeedToken.hash(token);
254
+
255
+ expect(CalendarFeedToken.isHashEqual(digest, digest)).toBe(true);
256
+ expect(
257
+ CalendarFeedToken.isHashEqual(
258
+ digest,
259
+ CalendarFeedToken.hash(CalendarFeedToken.mint()),
260
+ ),
261
+ ).toBe(false);
262
+ });
263
+
264
+ test("answers false on a length mismatch instead of throwing", () => {
265
+ const digest: string = CalendarFeedToken.hash(CalendarFeedToken.mint());
266
+
267
+ expect(CalendarFeedToken.isHashEqual(digest, digest.slice(0, 63))).toBe(
268
+ false,
269
+ );
270
+ expect(CalendarFeedToken.isHashEqual("", digest)).toBe(false);
271
+ expect(CalendarFeedToken.isHashEqual("", "")).toBe(true);
272
+ });
273
+ });
274
+
275
+ describe("CalendarFeedToken.applyTokenColumnsOnCreate", () => {
276
+ function expectConsistent(data: CalendarFeedTokenColumns): void {
277
+ expect(CalendarFeedToken.isValidShape(data.token)).toBe(true);
278
+ expect(data.tokenHash).toBe(CalendarFeedToken.hash(data.token as string));
279
+ expect(data.tokenHint).toBe(CalendarFeedToken.hint(data.token as string));
280
+ expect(data.rotatedAt).toBeInstanceOf(Date);
281
+ expect(data.previousTokenHash).toBeUndefined();
282
+ expect(data.previousTokenExpiresAt).toBeUndefined();
283
+ }
284
+
285
+ test("mints a consistent set onto an empty row", () => {
286
+ const data: CalendarFeedTokenColumns = {};
287
+ const minted: MintedCalendarFeedToken =
288
+ CalendarFeedToken.applyTokenColumnsOnCreate(data, {
289
+ trustSuppliedToken: true,
290
+ });
291
+
292
+ expectConsistent(data);
293
+ expect(minted).toEqual({
294
+ token: data.token,
295
+ tokenHash: data.tokenHash,
296
+ tokenHint: data.tokenHint,
297
+ });
298
+ });
299
+
300
+ test("keeps a trusted, well-formed token and derives hash and hint from it", () => {
301
+ const token: string = CalendarFeedToken.mint();
302
+ const data: CalendarFeedTokenColumns = {
303
+ token,
304
+ tokenHash: "chosen",
305
+ tokenHint: "zzzz",
306
+ };
307
+
308
+ CalendarFeedToken.applyTokenColumnsOnCreate(data, {
309
+ trustSuppliedToken: true,
310
+ });
311
+
312
+ expect(data.token).toBe(token);
313
+ expect(data.tokenHash).toBe(CalendarFeedToken.hash(token));
314
+ expect(data.tokenHint).toBe(CalendarFeedToken.hint(token));
315
+ });
316
+
317
+ test("discards a supplied token when it is not trusted", () => {
318
+ const token: string = CalendarFeedToken.mint();
319
+ const data: CalendarFeedTokenColumns = {
320
+ token,
321
+ tokenHash: CalendarFeedToken.hash(token),
322
+ };
323
+
324
+ CalendarFeedToken.applyTokenColumnsOnCreate(data, {
325
+ trustSuppliedToken: false,
326
+ });
327
+
328
+ expect(data.token).not.toBe(token);
329
+ expect(data.tokenHash).not.toBe(CalendarFeedToken.hash(token));
330
+ expectConsistent(data);
331
+ });
332
+
333
+ test("replaces a malformed token even when trusted", () => {
334
+ const data: CalendarFeedTokenColumns = { token: "nope" };
335
+
336
+ CalendarFeedToken.applyTokenColumnsOnCreate(data, {
337
+ trustSuppliedToken: true,
338
+ });
339
+
340
+ expect(data.token).not.toBe("nope");
341
+ expectConsistent(data);
342
+ });
343
+
344
+ test("never trusts a bare hash without its token", () => {
345
+ const data: CalendarFeedTokenColumns = { tokenHash: "chosen-hash" };
346
+
347
+ CalendarFeedToken.applyTokenColumnsOnCreate(data, {
348
+ trustSuppliedToken: true,
349
+ });
350
+
351
+ expect(data.tokenHash).not.toBe("chosen-hash");
352
+ expectConsistent(data);
353
+ });
354
+
355
+ test("clears any grace-period columns the caller set and stamps rotatedAt now", () => {
356
+ const before: number = Date.now();
357
+ const data: CalendarFeedTokenColumns = {
358
+ previousTokenHash: "stale",
359
+ previousTokenExpiresAt: new Date(0),
360
+ rotatedAt: new Date(0),
361
+ };
362
+
363
+ CalendarFeedToken.applyTokenColumnsOnCreate(data, {
364
+ trustSuppliedToken: false,
365
+ });
366
+
367
+ expect(data.previousTokenHash).toBeUndefined();
368
+ expect(data.previousTokenExpiresAt).toBeUndefined();
369
+ expect(data.rotatedAt!.getTime()).toBeGreaterThanOrEqual(before);
370
+ expect(data.rotatedAt!.getTime()).toBeLessThanOrEqual(Date.now());
371
+ });
372
+ });
373
+
374
+ describe("CalendarFeedToken.buildRotation", () => {
375
+ test("mints a new consistent token and parks the old hash for the grace period", () => {
376
+ const now: Date = new Date("2026-09-01T12:00:00.000Z");
377
+ const rotation: CalendarFeedRotation = CalendarFeedToken.buildRotation({
378
+ currentTokenHash: "old-hash",
379
+ now,
380
+ });
381
+
382
+ expect(CalendarFeedToken.isValidShape(rotation.token)).toBe(true);
383
+ expect(rotation.tokenHash).toBe(CalendarFeedToken.hash(rotation.token));
384
+ expect(rotation.tokenHint).toBe(CalendarFeedToken.hint(rotation.token));
385
+ expect(rotation.tokenHash).not.toBe("old-hash");
386
+ expect(rotation.rotatedAt).toBe(now);
387
+ expect(rotation.previousTokenHash).toBe("old-hash");
388
+
389
+ const graceMs: number = PREVIOUS_TOKEN_GRACE_DAYS * 24 * 60 * 60 * 1000;
390
+ const expiresAt: number = rotation.previousTokenExpiresAt!.getTime();
391
+ // moment.add(days) is DST-aware; allow an hour either side.
392
+ expect(Math.abs(expiresAt - (now.getTime() + graceMs))).toBeLessThanOrEqual(
393
+ 60 * 60 * 1000,
394
+ );
395
+ expect(PREVIOUS_TOKEN_GRACE_DAYS).toBe(30);
396
+ });
397
+
398
+ test("a first mint (no current hash) parks nothing", () => {
399
+ for (const current of [undefined, null, ""]) {
400
+ const rotation: CalendarFeedRotation = CalendarFeedToken.buildRotation({
401
+ currentTokenHash: current,
402
+ });
403
+
404
+ expect(rotation.previousTokenHash).toBeNull();
405
+ expect(rotation.previousTokenExpiresAt).toBeNull();
406
+ expect(rotation.rotatedAt).toBeInstanceOf(Date);
407
+ }
408
+ });
409
+
410
+ test("defaults `now` to the current time", () => {
411
+ const before: number = Date.now();
412
+ const rotation: CalendarFeedRotation = CalendarFeedToken.buildRotation({
413
+ currentTokenHash: "old-hash",
414
+ });
415
+
416
+ expect(rotation.rotatedAt.getTime()).toBeGreaterThanOrEqual(before);
417
+ expect(rotation.rotatedAt.getTime()).toBeLessThanOrEqual(Date.now());
418
+ });
419
+
420
+ test("two rotations never share a token", () => {
421
+ const a: CalendarFeedRotation = CalendarFeedToken.buildRotation({
422
+ currentTokenHash: "x",
423
+ });
424
+ const b: CalendarFeedRotation = CalendarFeedToken.buildRotation({
425
+ currentTokenHash: "x",
426
+ });
427
+
428
+ expect(a.token).not.toBe(b.token);
429
+ });
430
+ });
431
+
432
+ describe("CalendarFeedToken.toRotationUpdateData", () => {
433
+ test("carries exactly the six token columns, nulls included", () => {
434
+ const rotation: CalendarFeedRotation = CalendarFeedToken.buildRotation({
435
+ currentTokenHash: "old-hash",
436
+ now: new Date("2026-09-01T12:00:00.000Z"),
437
+ });
438
+
439
+ const data: CalendarFeedRotationUpdateData =
440
+ CalendarFeedToken.toRotationUpdateData(rotation);
441
+
442
+ expect(Object.keys(data).sort()).toEqual(
443
+ [
444
+ "token",
445
+ "tokenHash",
446
+ "tokenHint",
447
+ "rotatedAt",
448
+ "previousTokenHash",
449
+ "previousTokenExpiresAt",
450
+ ].sort(),
451
+ );
452
+ expect(data.token).toBe(rotation.token);
453
+ expect(data.tokenHash).toBe(rotation.tokenHash);
454
+ expect(data.tokenHint).toBe(rotation.tokenHint);
455
+ expect(data.rotatedAt).toBe(rotation.rotatedAt);
456
+ expect(data.previousTokenHash).toBe("old-hash");
457
+ expect(data.previousTokenExpiresAt).toBe(rotation.previousTokenExpiresAt);
458
+
459
+ const first: CalendarFeedRotationUpdateData =
460
+ CalendarFeedToken.toRotationUpdateData(
461
+ CalendarFeedToken.buildRotation({ currentTokenHash: null }),
462
+ );
463
+
464
+ expect(first.previousTokenHash).toBeNull();
465
+ expect(first.previousTokenExpiresAt).toBeNull();
466
+ });
467
+ });