@oneuptime/common 12.0.4 → 12.0.5

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 (374) hide show
  1. package/Models/DatabaseModels/AIRun.ts +38 -1
  2. package/Models/DatabaseModels/AlertFeed.ts +30 -0
  3. package/Models/DatabaseModels/Dashboard.ts +28 -0
  4. package/Models/DatabaseModels/Incident.ts +2 -0
  5. package/Models/DatabaseModels/IncidentEpisode.ts +1 -0
  6. package/Models/DatabaseModels/IncidentEpisodePublicNote.ts +1 -0
  7. package/Models/DatabaseModels/IncidentEpisodeStateTimeline.ts +1 -0
  8. package/Models/DatabaseModels/IncidentFeed.ts +30 -0
  9. package/Models/DatabaseModels/IncidentPublicNote.ts +1 -0
  10. package/Models/DatabaseModels/IncidentStateTimeline.ts +1 -0
  11. package/Models/DatabaseModels/LogSavedView.ts +33 -0
  12. package/Models/DatabaseModels/Project.ts +231 -9
  13. package/Models/DatabaseModels/ScheduledMaintenance.ts +1 -0
  14. package/Models/DatabaseModels/ScheduledMaintenancePublicNote.ts +1 -0
  15. package/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.ts +1 -0
  16. package/Models/DatabaseModels/ScheduledMaintenanceTemplateOwnerUser.ts +4 -4
  17. package/Models/DatabaseModels/StatusPage.ts +28 -0
  18. package/Models/DatabaseModels/StatusPageAnnouncement.ts +1 -0
  19. package/Server/API/AIAgentDataAPI.ts +44 -55
  20. package/Server/API/AIInvestigationAPI.ts +175 -17
  21. package/Server/API/DashboardAPI.ts +10 -10
  22. package/Server/API/StatusPageAPI.ts +38 -19
  23. package/Server/EnvironmentConfig.ts +16 -0
  24. package/Server/Infrastructure/ClickhouseConfig.ts +15 -1
  25. package/Server/Infrastructure/ClickhouseDatabase.ts +1 -1
  26. package/Server/Infrastructure/Postgres/SchemaMigrations/1786096660558-AddTimeRangeToLogSavedView.ts +27 -0
  27. package/Server/Infrastructure/Postgres/SchemaMigrations/1786101798351-MigrationName.ts +89 -0
  28. package/Server/Infrastructure/Postgres/SchemaMigrations/1786105470826-MigrationName.ts +27 -0
  29. package/Server/Infrastructure/Postgres/SchemaMigrations/1786400000000-AddMasterPasswordSalt.ts +40 -0
  30. package/Server/Infrastructure/Postgres/SchemaMigrations/1786500000000-AddInvestigationCodeFixRecommendation.ts +26 -0
  31. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
  32. package/Server/Services/AIRunService.ts +78 -10
  33. package/Server/Services/AIService.ts +94 -22
  34. package/Server/Services/AlertFeedService.ts +5 -0
  35. package/Server/Services/AnalyticsDatabaseService.ts +29 -0
  36. package/Server/Services/DatabaseService.ts +43 -2
  37. package/Server/Services/IncidentFeedService.ts +5 -0
  38. package/Server/Services/LlmLogService.ts +46 -2
  39. package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +6 -0
  40. package/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.ts +4 -0
  41. package/Server/Utils/AI/CodeFix/CodeFixReadiness.ts +8 -5
  42. package/Server/Utils/AI/CodeFix/FixRunBudget.ts +90 -16
  43. package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +2 -0
  44. package/Server/Utils/AI/Remediation/RemediationPlanRunner.ts +2 -0
  45. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +223 -22
  46. package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +25 -24
  47. package/Server/Utils/AI/SRE/ConfidenceSignal.ts +182 -71
  48. package/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.ts +252 -43
  49. package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +25 -24
  50. package/Server/Utils/AI/SRE/InstrumentationTaskTrigger.ts +47 -13
  51. package/Server/Utils/AI/SRE/InvestigationGrader.ts +9 -5
  52. package/Server/Utils/AI/SRE/InvestigationQueue.ts +141 -24
  53. package/Server/Utils/AI/SRE/InvestigationSubjectLock.ts +66 -0
  54. package/Server/Utils/AI/SRE/PostedRootCause.ts +94 -3
  55. package/Server/Utils/AI/SRE/README.md +1 -1
  56. package/Server/Utils/AI/SRE/SubjectCodeFixRun.ts +132 -10
  57. package/Server/Utils/AnalyticsDatabase/ClusterConfig.ts +24 -0
  58. package/Server/Utils/Monitor/Criteria/APIRequestCriteria.ts +29 -0
  59. package/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.ts +56 -0
  60. package/Server/Utils/Monitor/MonitorMetricUtil.ts +46 -0
  61. package/Server/Utils/Monitor/MonitorTemplateUtil.ts +1 -0
  62. package/Server/Utils/PasswordHash.ts +28 -4
  63. package/Tests/App/Dashboard/AIInvestigationHeaderStatus.test.tsx +251 -0
  64. package/Tests/App/Dashboard/EventStatusPanel.test.tsx +747 -0
  65. package/Tests/App/Dashboard/InvestigationFeedRefresh.test.tsx +480 -0
  66. package/Tests/App/Dashboard/InvestigationPanel.test.tsx +1184 -0
  67. package/Tests/App/Dashboard/InvestigationPanelStatus.test.tsx +500 -0
  68. package/Tests/App/Dashboard/PortMonitorCriteriaFilter.test.ts +93 -0
  69. package/Tests/App/StatusPage/PublicStatusPageAPIErrorHandling.test.ts +117 -0
  70. package/Tests/App/StatusPage/StatusPageModelAPIPolymorphism.test.ts +223 -0
  71. package/Tests/Models/DatabaseModels/PermissionCatalogueCoverage.test.ts +243 -0
  72. package/Tests/Server/API/AIAgentDataFixFromIncidentContext.test.ts +266 -0
  73. package/Tests/Server/API/AIInvestigationAPI.test.ts +594 -0
  74. package/Tests/Server/API/AIInvestigationCreateFixTask.test.ts +80 -0
  75. package/Tests/Server/API/DashboardMasterPasswordAPI.test.ts +172 -1
  76. package/Tests/Server/API/StatusPageMasterPasswordAPI.test.ts +335 -0
  77. package/Tests/Server/Services/AIRunCodeFixClaim.test.ts +59 -5
  78. package/Tests/Server/Services/AIRunHumanVerdict.test.ts +127 -58
  79. package/Tests/Server/Services/AIServiceDailyBudget.test.ts +396 -20
  80. package/Tests/Server/Services/AddMasterPasswordSaltMigration.test.ts +283 -0
  81. package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +25 -1
  82. package/Tests/Server/Services/DatabaseServicePerUserPasswordSalt.test.ts +45 -35
  83. package/Tests/Server/Services/DatabaseServiceUpdateColumnsWithoutHooks.test.ts +54 -0
  84. package/Tests/Server/Services/FeedAIRunAssociation.test.ts +125 -0
  85. package/Tests/Server/Services/FixFromIncidentTaskTrigger.test.ts +418 -57
  86. package/Tests/Server/Services/InstrumentationTaskTrigger.test.ts +173 -14
  87. package/Tests/Server/Services/MasterPasswordScrypt.test.ts +567 -0
  88. package/Tests/Server/Services/SeparateIncidentAlertAiSettingsMigration.test.ts +194 -0
  89. package/Tests/Server/Utils/AI/AIAlertGating.test.ts +37 -4
  90. package/Tests/Server/Utils/AI/AIConfidenceSignal.test.ts +330 -29
  91. package/Tests/Server/Utils/AI/AIIncidentGating.test.ts +25 -0
  92. package/Tests/Server/Utils/AI/AIInvestigationQueue.test.ts +293 -5
  93. package/Tests/Server/Utils/AI/CodeFixAgentCompletion.test.ts +59 -1
  94. package/Tests/Server/Utils/AI/FixFromIncidentAutoTrigger.test.ts +342 -19
  95. package/Tests/Server/Utils/AI/FixRunBudget.test.ts +214 -30
  96. package/Tests/Server/Utils/AI/Insights/InvestigationQueueInsightSubject.test.ts +2 -0
  97. package/Tests/Server/Utils/AI/InvestigationGrader.test.ts +65 -17
  98. package/Tests/Server/Utils/AI/InvestigationQueueRemediationExecution.test.ts +3 -0
  99. package/Tests/Server/Utils/AI/InvestigationSettlement.test.ts +313 -20
  100. package/Tests/Server/Utils/AI/PostedRootCause.test.ts +254 -0
  101. package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +4 -0
  102. package/Tests/Server/Utils/AI/RemediationPlanRunner.test.ts +2 -0
  103. package/Tests/Server/Utils/AI/SubjectCodeFixRunDedupe.test.ts +44 -0
  104. package/Tests/Server/Utils/AnalyticsDatabase/ClusterAwareSchema.test.ts +30 -0
  105. package/Tests/Server/Utils/Monitor/Criteria/APIRequestCriteriaPortTimings.test.ts +212 -0
  106. package/Tests/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.test.ts +401 -0
  107. package/Tests/Server/Utils/Monitor/MonitorMetricUtilPortTimings.test.ts +243 -0
  108. package/Tests/Server/Utils/PasswordHash.test.ts +30 -0
  109. package/Tests/Types/Dashboard/DashboardTemplates.test.ts +111 -0
  110. package/Tests/Types/Monitor/CephMetricCatalog.test.ts +104 -0
  111. package/Tests/Types/Monitor/CriteriaFilter.test.ts +4 -0
  112. package/Tests/Types/Monitor/DockerMetricCatalog.test.ts +109 -0
  113. package/Tests/Types/Monitor/DockerSwarmMetricCatalog.test.ts +118 -0
  114. package/Tests/Types/Monitor/HostMetricCatalog.test.ts +104 -0
  115. package/Tests/Types/Monitor/IotMetricCatalog.test.ts +104 -0
  116. package/Tests/Types/Monitor/KubernetesMetricCatalog.test.ts +118 -0
  117. package/Tests/Types/Monitor/MonitorCriteriaInstance.test.ts +42 -0
  118. package/Tests/Types/Monitor/MonitorStep.test.ts +38 -0
  119. package/Tests/Types/Monitor/MonitorStepDomainMonitor.test.ts +75 -0
  120. package/Tests/Types/Monitor/PodmanMetricCatalog.test.ts +109 -0
  121. package/Tests/Types/Monitor/ProxmoxMetricCatalog.test.ts +107 -0
  122. package/Tests/Types/Monitor/SnmpMonitor/SnmpVendorTemplate.test.ts +151 -0
  123. package/Tests/Types/Permission.test.ts +137 -0
  124. package/Tests/Types/Rum/SessionReplayMaskingMode.test.ts +101 -0
  125. package/Tests/UI/Components/AiInvestigationSettingsCard.test.tsx +5 -5
  126. package/Tests/UI/Components/CardModelDetailEdit.test.tsx +3 -5
  127. package/Tests/UI/Components/ErrorBoundary.test.tsx +158 -0
  128. package/Tests/UI/Components/FeedItemSafeMode.test.tsx +74 -0
  129. package/Tests/UI/Components/MoreMenu.test.tsx +756 -0
  130. package/Tests/UI/Components/StatusPage/ResourceGroupSection.test.tsx +62 -0
  131. package/Tests/UI/Monitor/PortMonitorView.test.tsx +237 -0
  132. package/Tests/UI/Telemetry/TelemetrySnapshotWindowAlert.test.tsx +91 -0
  133. package/Tests/UI/Utils/DownloadFile.test.ts +103 -0
  134. package/Tests/UI/Utils/ErrorSupportBundle.test.ts +582 -0
  135. package/Tests/UI/Utils/Project.test.ts +31 -0
  136. package/Tests/UI/Utils/StatusPageModelAPIInjection.test.ts +245 -0
  137. package/Tests/UI/Utils/UseDashboardGridDnd.test.tsx +820 -0
  138. package/Tests/Utils/Dashboard/DashboardViewConfig.test.ts +329 -0
  139. package/Tests/Utils/Dashboard/GridLayout.test.ts +951 -0
  140. package/Tests/Utils/Monitor/MonitorMetricType.test.ts +117 -0
  141. package/Tests/Utils/RecordingRuleExpression.test.ts +327 -0
  142. package/Tests/Utils/StatusPage/GroupNestingLayout.test.ts +186 -0
  143. package/Tests/Utils/StatusPage/GroupTree.test.ts +456 -0
  144. package/Tests/Utils/StatusPage/OverviewGroupHierarchyVisibility.test.ts +588 -0
  145. package/Tests/Utils/Telemetry/SavedViewTimeRange.test.ts +244 -0
  146. package/Tests/Utils/Telemetry/TelemetryQueryTimeRange.test.ts +825 -0
  147. package/Types/AI/AIRunCodeFixRecommendation.ts +17 -0
  148. package/Types/AI/CodeFixTaskContext.ts +63 -3
  149. package/Types/AI/CodeFixTaskType.ts +3 -3
  150. package/Types/HashedString.ts +4 -3
  151. package/Types/Monitor/CriteriaFilter.ts +4 -0
  152. package/Types/Monitor/DomainMonitor/DomainLookupMethod.ts +23 -0
  153. package/Types/Monitor/DomainMonitor/DomainMonitorResponse.ts +7 -0
  154. package/Types/Monitor/MonitorCriteriaInstance.ts +24 -7
  155. package/Types/Monitor/MonitorMetricType.ts +8 -0
  156. package/Types/Monitor/MonitorStep.ts +10 -1
  157. package/Types/Monitor/MonitorStepDomainMonitor.ts +21 -0
  158. package/Types/Monitor/PortMonitor/PortMonitorTimings.ts +10 -0
  159. package/Types/Permission.ts +23 -175
  160. package/Types/Probe/ProbeMonitorResponse.ts +6 -0
  161. package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +2 -3
  162. package/UI/Components/ErrorBoundary.tsx +138 -11
  163. package/UI/Components/Feed/FeedItem.tsx +9 -2
  164. package/UI/Components/ModelDetail/CardModelDetail.tsx +0 -12
  165. package/UI/Components/ModelTable/BaseModelTable.tsx +1 -1
  166. package/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.ts +12 -2
  167. package/UI/Components/MoreMenu/MoreMenu.tsx +285 -97
  168. package/UI/Components/MoreMenu/MoreMenuItem.tsx +8 -5
  169. package/UI/Utils/DownloadFile.ts +16 -8
  170. package/UI/Utils/ErrorSupportBundle.ts +805 -0
  171. package/UI/Utils/ModelAPI/ModelAPI.ts +19 -8
  172. package/UI/Utils/Project.ts +4 -0
  173. package/UI/Utils/StatusPage.ts +8 -2
  174. package/UI/Utils/UseDashboardGridDnd.ts +917 -0
  175. package/Utils/Dashboard/DashboardViewConfig.ts +80 -36
  176. package/Utils/Dashboard/GridLayout.ts +523 -0
  177. package/Utils/Metrics/RecordingRuleExpression.ts +19 -17
  178. package/Utils/Monitor/MonitorMetricType.ts +44 -2
  179. package/Utils/StatusPage/GroupNestingLayout.ts +69 -0
  180. package/Utils/StatusPage/GroupTree.ts +157 -66
  181. package/Utils/StatusPage/ResourceUptime.ts +37 -2
  182. package/Utils/Telemetry/SavedViewTimeRange.ts +114 -0
  183. package/Utils/Telemetry/TelemetryQueryTimeRange.ts +334 -0
  184. package/build/dist/Models/DatabaseModels/AIRun.js +39 -1
  185. package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
  186. package/build/dist/Models/DatabaseModels/AlertFeed.js +32 -0
  187. package/build/dist/Models/DatabaseModels/AlertFeed.js.map +1 -1
  188. package/build/dist/Models/DatabaseModels/Dashboard.js +29 -0
  189. package/build/dist/Models/DatabaseModels/Dashboard.js.map +1 -1
  190. package/build/dist/Models/DatabaseModels/Incident.js +2 -0
  191. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  192. package/build/dist/Models/DatabaseModels/IncidentEpisode.js +1 -0
  193. package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
  194. package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js +1 -0
  195. package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js.map +1 -1
  196. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js +1 -0
  197. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js.map +1 -1
  198. package/build/dist/Models/DatabaseModels/IncidentFeed.js +32 -0
  199. package/build/dist/Models/DatabaseModels/IncidentFeed.js.map +1 -1
  200. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js +1 -0
  201. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js.map +1 -1
  202. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js +1 -0
  203. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js.map +1 -1
  204. package/build/dist/Models/DatabaseModels/LogSavedView.js +33 -0
  205. package/build/dist/Models/DatabaseModels/LogSavedView.js.map +1 -1
  206. package/build/dist/Models/DatabaseModels/Project.js +241 -11
  207. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  208. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +1 -0
  209. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
  210. package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js +1 -0
  211. package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js.map +1 -1
  212. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js +1 -0
  213. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js.map +1 -1
  214. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplateOwnerUser.js +4 -4
  215. package/build/dist/Models/DatabaseModels/StatusPage.js +29 -0
  216. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  217. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js +1 -0
  218. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js.map +1 -1
  219. package/build/dist/Server/API/AIAgentDataAPI.js +28 -46
  220. package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
  221. package/build/dist/Server/API/AIInvestigationAPI.js +111 -11
  222. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  223. package/build/dist/Server/API/DashboardAPI.js +8 -5
  224. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  225. package/build/dist/Server/API/StatusPageAPI.js +29 -11
  226. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  227. package/build/dist/Server/EnvironmentConfig.js +14 -0
  228. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  229. package/build/dist/Server/Infrastructure/ClickhouseConfig.js +12 -1
  230. package/build/dist/Server/Infrastructure/ClickhouseConfig.js.map +1 -1
  231. package/build/dist/Server/Infrastructure/ClickhouseDatabase.js +1 -1
  232. package/build/dist/Server/Infrastructure/ClickhouseDatabase.js.map +1 -1
  233. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786096660558-AddTimeRangeToLogSavedView.js +22 -0
  234. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786096660558-AddTimeRangeToLogSavedView.js.map +1 -0
  235. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786101798351-MigrationName.js +36 -0
  236. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786101798351-MigrationName.js.map +1 -0
  237. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786105470826-MigrationName.js +18 -0
  238. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786105470826-MigrationName.js.map +1 -0
  239. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786400000000-AddMasterPasswordSalt.js +19 -0
  240. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786400000000-AddMasterPasswordSalt.js.map +1 -0
  241. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786500000000-AddInvestigationCodeFixRecommendation.js +19 -0
  242. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786500000000-AddInvestigationCodeFixRecommendation.js.map +1 -0
  243. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
  244. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  245. package/build/dist/Server/Services/AIRunService.js +58 -12
  246. package/build/dist/Server/Services/AIRunService.js.map +1 -1
  247. package/build/dist/Server/Services/AIService.js +74 -21
  248. package/build/dist/Server/Services/AIService.js.map +1 -1
  249. package/build/dist/Server/Services/AlertFeedService.js +3 -0
  250. package/build/dist/Server/Services/AlertFeedService.js.map +1 -1
  251. package/build/dist/Server/Services/AnalyticsDatabaseService.js +29 -1
  252. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  253. package/build/dist/Server/Services/DatabaseService.js +21 -2
  254. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  255. package/build/dist/Server/Services/IncidentFeedService.js +3 -0
  256. package/build/dist/Server/Services/IncidentFeedService.js.map +1 -1
  257. package/build/dist/Server/Services/LlmLogService.js +22 -1
  258. package/build/dist/Server/Services/LlmLogService.js.map +1 -1
  259. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +3 -0
  260. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
  261. package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js +4 -0
  262. package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js.map +1 -1
  263. package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js +8 -5
  264. package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js.map +1 -1
  265. package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js +63 -24
  266. package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js.map +1 -1
  267. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +2 -0
  268. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
  269. package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js +2 -0
  270. package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js.map +1 -1
  271. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +156 -28
  272. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  273. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +21 -19
  274. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
  275. package/build/dist/Server/Utils/AI/SRE/ConfidenceSignal.js +128 -49
  276. package/build/dist/Server/Utils/AI/SRE/ConfidenceSignal.js.map +1 -1
  277. package/build/dist/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.js +170 -31
  278. package/build/dist/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.js.map +1 -1
  279. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +21 -19
  280. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
  281. package/build/dist/Server/Utils/AI/SRE/InstrumentationTaskTrigger.js +33 -9
  282. package/build/dist/Server/Utils/AI/SRE/InstrumentationTaskTrigger.js.map +1 -1
  283. package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js +9 -5
  284. package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js.map +1 -1
  285. package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js +121 -41
  286. package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js.map +1 -1
  287. package/build/dist/Server/Utils/AI/SRE/InvestigationSubjectLock.js +46 -0
  288. package/build/dist/Server/Utils/AI/SRE/InvestigationSubjectLock.js.map +1 -0
  289. package/build/dist/Server/Utils/AI/SRE/PostedRootCause.js +61 -14
  290. package/build/dist/Server/Utils/AI/SRE/PostedRootCause.js.map +1 -1
  291. package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js +79 -1
  292. package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js.map +1 -1
  293. package/build/dist/Server/Utils/AnalyticsDatabase/ClusterConfig.js +21 -0
  294. package/build/dist/Server/Utils/AnalyticsDatabase/ClusterConfig.js.map +1 -1
  295. package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js +20 -3
  296. package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js.map +1 -1
  297. package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js +43 -0
  298. package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js.map +1 -1
  299. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +33 -0
  300. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
  301. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +1 -0
  302. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
  303. package/build/dist/Server/Utils/PasswordHash.js +25 -4
  304. package/build/dist/Server/Utils/PasswordHash.js.map +1 -1
  305. package/build/dist/Types/AI/AIRunCodeFixRecommendation.js +18 -0
  306. package/build/dist/Types/AI/AIRunCodeFixRecommendation.js.map +1 -0
  307. package/build/dist/Types/AI/CodeFixTaskContext.js +23 -3
  308. package/build/dist/Types/AI/CodeFixTaskContext.js.map +1 -1
  309. package/build/dist/Types/AI/CodeFixTaskType.js +3 -3
  310. package/build/dist/Types/HashedString.js +4 -3
  311. package/build/dist/Types/HashedString.js.map +1 -1
  312. package/build/dist/Types/Monitor/CriteriaFilter.js +4 -0
  313. package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
  314. package/build/dist/Types/Monitor/DomainMonitor/DomainLookupMethod.js +24 -0
  315. package/build/dist/Types/Monitor/DomainMonitor/DomainLookupMethod.js.map +1 -0
  316. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +24 -7
  317. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
  318. package/build/dist/Types/Monitor/MonitorMetricType.js +7 -0
  319. package/build/dist/Types/Monitor/MonitorMetricType.js.map +1 -1
  320. package/build/dist/Types/Monitor/MonitorStep.js +6 -1
  321. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  322. package/build/dist/Types/Monitor/MonitorStepDomainMonitor.js +15 -0
  323. package/build/dist/Types/Monitor/MonitorStepDomainMonitor.js.map +1 -1
  324. package/build/dist/Types/Monitor/PortMonitor/PortMonitorTimings.js +2 -0
  325. package/build/dist/Types/Monitor/PortMonitor/PortMonitorTimings.js.map +1 -0
  326. package/build/dist/Types/Permission.js +23 -155
  327. package/build/dist/Types/Permission.js.map +1 -1
  328. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +2 -5
  329. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
  330. package/build/dist/UI/Components/ErrorBoundary.js +67 -8
  331. package/build/dist/UI/Components/ErrorBoundary.js.map +1 -1
  332. package/build/dist/UI/Components/Feed/FeedItem.js +2 -2
  333. package/build/dist/UI/Components/Feed/FeedItem.js.map +1 -1
  334. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +0 -12
  335. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
  336. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +1 -1
  337. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  338. package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js +12 -2
  339. package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js.map +1 -1
  340. package/build/dist/UI/Components/MoreMenu/MoreMenu.js +193 -65
  341. package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
  342. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +1 -1
  343. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
  344. package/build/dist/UI/Utils/DownloadFile.js +15 -8
  345. package/build/dist/UI/Utils/DownloadFile.js.map +1 -1
  346. package/build/dist/UI/Utils/ErrorSupportBundle.js +476 -0
  347. package/build/dist/UI/Utils/ErrorSupportBundle.js.map +1 -0
  348. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +18 -8
  349. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
  350. package/build/dist/UI/Utils/Project.js +3 -0
  351. package/build/dist/UI/Utils/Project.js.map +1 -1
  352. package/build/dist/UI/Utils/StatusPage.js +4 -4
  353. package/build/dist/UI/Utils/StatusPage.js.map +1 -1
  354. package/build/dist/UI/Utils/UseDashboardGridDnd.js +551 -0
  355. package/build/dist/UI/Utils/UseDashboardGridDnd.js.map +1 -0
  356. package/build/dist/Utils/Dashboard/DashboardViewConfig.js +42 -26
  357. package/build/dist/Utils/Dashboard/DashboardViewConfig.js.map +1 -1
  358. package/build/dist/Utils/Dashboard/GridLayout.js +338 -0
  359. package/build/dist/Utils/Dashboard/GridLayout.js.map +1 -0
  360. package/build/dist/Utils/Metrics/RecordingRuleExpression.js +19 -17
  361. package/build/dist/Utils/Metrics/RecordingRuleExpression.js.map +1 -1
  362. package/build/dist/Utils/Monitor/MonitorMetricType.js +38 -6
  363. package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
  364. package/build/dist/Utils/StatusPage/GroupNestingLayout.js +46 -0
  365. package/build/dist/Utils/StatusPage/GroupNestingLayout.js.map +1 -1
  366. package/build/dist/Utils/StatusPage/GroupTree.js +79 -34
  367. package/build/dist/Utils/StatusPage/GroupTree.js.map +1 -1
  368. package/build/dist/Utils/StatusPage/ResourceUptime.js +22 -1
  369. package/build/dist/Utils/StatusPage/ResourceUptime.js.map +1 -1
  370. package/build/dist/Utils/Telemetry/SavedViewTimeRange.js +79 -0
  371. package/build/dist/Utils/Telemetry/SavedViewTimeRange.js.map +1 -0
  372. package/build/dist/Utils/Telemetry/TelemetryQueryTimeRange.js +236 -0
  373. package/build/dist/Utils/Telemetry/TelemetryQueryTimeRange.js.map +1 -0
  374. package/package.json +1 -1
@@ -0,0 +1,567 @@
1
+ import Dashboard from "../../../Models/DatabaseModels/Dashboard";
2
+ import StatusPage from "../../../Models/DatabaseModels/StatusPage";
3
+ import { EncryptionSecret } from "../../../Server/EnvironmentConfig";
4
+ import DashboardService from "../../../Server/Services/DashboardService";
5
+ import StatusPageService from "../../../Server/Services/StatusPageService";
6
+ import DatabaseRequestType from "../../../Server/Types/BaseDatabase/DatabaseRequestType";
7
+ import ColumnPermissions from "../../../Server/Types/Database/Permissions/ColumnPermission";
8
+ import PasswordHash from "../../../Server/Utils/PasswordHash";
9
+ import DatabaseCommonInteractionProps from "../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
10
+ import ColumnLength from "../../../Types/Database/ColumnLength";
11
+ import { TableColumnMetadata } from "../../../Types/Database/TableColumn";
12
+ import TableColumnType from "../../../Types/Database/TableColumnType";
13
+ import BadDataException from "../../../Types/Exception/BadDataException";
14
+ import HashedString from "../../../Types/HashedString";
15
+ import { JSONObject } from "../../../Types/JSON";
16
+ import ObjectID from "../../../Types/ObjectID";
17
+ import CryptoJS from "crypto-js";
18
+ /*
19
+ * `jest` is deliberately NOT imported from @jest/globals here. The value from
20
+ * that package is jest-mock's, whose spyOn returns SpyInstance<Fn>, while the
21
+ * `jest.SpiedFunction` used to annotate those spies below resolves to the
22
+ * global @types/jest namespace, where it means SpyInstance<ReturnType<Fn>,
23
+ * ArgsType<Fn>>. Mixing the two makes every annotated spy a type error. Every
24
+ * other suite in this tree uses the global jest for exactly this reason.
25
+ */
26
+ import { afterEach, describe, expect, test } from "@jest/globals";
27
+
28
+ /*
29
+ * Dashboard and status-page master passwords are human-chosen credentials,
30
+ * just like account passwords. They therefore need the same deliberately
31
+ * expensive password KDF, a unique salt per protected resource, and an
32
+ * opportunistic upgrade path for the SHA-256 digests written by older
33
+ * versions.
34
+ *
35
+ * These tests exercise the shared DatabaseService write and verification
36
+ * pipeline directly. No Postgres is needed: upgrade writes are captured by a
37
+ * stub so the hash/salt pair can be checked as one atomic payload.
38
+ */
39
+
40
+ type ProtectedResource = Dashboard | StatusPage;
41
+
42
+ type HashFunction<T> = (data: T) => Promise<T>;
43
+
44
+ type SanitizeFunction = (
45
+ data: unknown,
46
+ props: DatabaseCommonInteractionProps,
47
+ isUpdate?: boolean,
48
+ ) => Promise<JSONObject>;
49
+
50
+ type UpdateColumnsInput = {
51
+ id: ObjectID;
52
+ data: JSONObject;
53
+ expectedData?: JSONObject;
54
+ skipUpdateDateColumn?: boolean;
55
+ };
56
+
57
+ type ResourceService = {
58
+ hash: HashFunction<ProtectedResource>;
59
+ sanitizeCreateOrUpdate: SanitizeFunction;
60
+ verifyHashedColumnValue: (data: {
61
+ item: ProtectedResource;
62
+ columnName: string;
63
+ plainValue: string;
64
+ }) => Promise<boolean>;
65
+ updateColumnsByIdWithoutHooks: (input: UpdateColumnsInput) => Promise<void>;
66
+ };
67
+
68
+ const dashboardService: ResourceService =
69
+ DashboardService as unknown as ResourceService;
70
+ const statusPageService: ResourceService =
71
+ StatusPageService as unknown as ResourceService;
72
+
73
+ type ResourceCase = {
74
+ name: string;
75
+ model: Dashboard | StatusPage;
76
+ service: ResourceService;
77
+ };
78
+
79
+ type ResourceTestCase = {
80
+ name: string;
81
+ resource: ProtectedResource;
82
+ service: ResourceService;
83
+ };
84
+
85
+ type ResourcePairTestCase = {
86
+ name: string;
87
+ first: ProtectedResource;
88
+ second: ProtectedResource;
89
+ service: ResourceService;
90
+ };
91
+
92
+ const resourceCases: Array<ResourceCase> = [
93
+ {
94
+ name: "Dashboard",
95
+ model: new Dashboard(),
96
+ service: dashboardService,
97
+ },
98
+ {
99
+ name: "StatusPage",
100
+ model: new StatusPage(),
101
+ service: statusPageService,
102
+ },
103
+ ];
104
+
105
+ const setPlainMasterPassword: (
106
+ resource: ProtectedResource,
107
+ password: string,
108
+ ) => void = (resource: ProtectedResource, password: string): void => {
109
+ resource.masterPassword = new HashedString(password);
110
+ };
111
+
112
+ const setStoredMasterPassword: (
113
+ resource: ProtectedResource,
114
+ hash: string,
115
+ salt?: string,
116
+ ) => void = (
117
+ resource: ProtectedResource,
118
+ hash: string,
119
+ salt?: string,
120
+ ): void => {
121
+ resource._id = ObjectID.generate().toString();
122
+ resource.masterPassword = new HashedString(hash, true);
123
+
124
+ /*
125
+ * Clear rather than assign undefined: exactOptionalPropertyTypes forbids
126
+ * writing undefined to an optional string, and these resources are shared
127
+ * across the test.each cases, so a salt left by an earlier case has to
128
+ * actually go away when this one asks for a legacy, saltless row.
129
+ */
130
+ if (salt === undefined) {
131
+ delete resource.masterPasswordSalt;
132
+ } else {
133
+ resource.masterPasswordSalt = salt;
134
+ }
135
+ };
136
+
137
+ const makeCurrentResource: (data: {
138
+ resource: ProtectedResource;
139
+ password: string;
140
+ }) => Promise<ProtectedResource> = async (data: {
141
+ resource: ProtectedResource;
142
+ password: string;
143
+ }): Promise<ProtectedResource> => {
144
+ const salt: string = PasswordHash.generateSalt();
145
+
146
+ setStoredMasterPassword(
147
+ data.resource,
148
+ await PasswordHash.hash({
149
+ plainValue: data.password,
150
+ salt,
151
+ }),
152
+ salt,
153
+ );
154
+
155
+ return data.resource;
156
+ };
157
+
158
+ afterEach(() => {
159
+ jest.restoreAllMocks();
160
+ });
161
+
162
+ describe("Master-password model metadata", () => {
163
+ test.each(resourceCases)(
164
+ "$name.masterPassword declares masterPasswordSalt",
165
+ ({ model }: ResourceCase) => {
166
+ const metadata: TableColumnMetadata =
167
+ model.getTableColumnMetadata("masterPassword");
168
+
169
+ expect(metadata.hashed).toBe(true);
170
+ expect(metadata.hashSaltColumn).toBe("masterPasswordSalt");
171
+ expect(model.hasColumn("masterPasswordSalt")).toBe(true);
172
+ },
173
+ );
174
+
175
+ test.each(resourceCases)(
176
+ "$name.masterPasswordSalt is server-computed and API-inaccessible",
177
+ ({ model }: ResourceCase) => {
178
+ const metadata: TableColumnMetadata =
179
+ model.getTableColumnMetadata("masterPasswordSalt");
180
+ const accessControl: {
181
+ create?: Array<unknown>;
182
+ read?: Array<unknown>;
183
+ update?: Array<unknown>;
184
+ } = model.getColumnAccessControlFor("masterPasswordSalt") as {
185
+ create?: Array<unknown>;
186
+ read?: Array<unknown>;
187
+ update?: Array<unknown>;
188
+ };
189
+
190
+ expect(metadata.computed).toBe(true);
191
+ expect(metadata.type).toBe(TableColumnType.ShortText);
192
+ expect(accessControl.create).toEqual([]);
193
+ expect(accessControl.read).toEqual([]);
194
+ expect(accessControl.update).toEqual([]);
195
+ },
196
+ );
197
+
198
+ test("generated salts fit the declared database column", () => {
199
+ expect(PasswordHash.generateSalt()).toMatch(/^[0-9a-f]{64}$/);
200
+ expect(PasswordHash.generateSalt().length).toBeLessThanOrEqual(
201
+ ColumnLength.ShortText,
202
+ );
203
+ });
204
+
205
+ test.each(resourceCases)(
206
+ "$name computed salt survives create permission validation",
207
+ ({ model }: ResourceCase) => {
208
+ model.masterPasswordSalt = PasswordHash.generateSalt();
209
+
210
+ expect(() => {
211
+ ColumnPermissions.checkDataColumnPermissions(
212
+ model instanceof Dashboard ? Dashboard : StatusPage,
213
+ model,
214
+ {} as DatabaseCommonInteractionProps,
215
+ DatabaseRequestType.Create,
216
+ );
217
+ }).not.toThrow();
218
+ },
219
+ );
220
+
221
+ test.each(resourceCases)(
222
+ "$name rejects a caller-supplied salt on update",
223
+ ({ model }: ResourceCase) => {
224
+ model.masterPasswordSalt = PasswordHash.generateSalt();
225
+
226
+ expect(() => {
227
+ ColumnPermissions.checkDataColumnPermissions(
228
+ model instanceof Dashboard ? Dashboard : StatusPage,
229
+ model,
230
+ {} as DatabaseCommonInteractionProps,
231
+ DatabaseRequestType.Update,
232
+ );
233
+ }).toThrow("not allowed to update on masterPasswordSalt");
234
+ },
235
+ );
236
+ });
237
+
238
+ describe("Master-password create path", () => {
239
+ test.each([
240
+ {
241
+ name: "Dashboard",
242
+ resource: new Dashboard() as ProtectedResource,
243
+ service: dashboardService,
244
+ },
245
+ {
246
+ name: "StatusPage",
247
+ resource: new StatusPage() as ProtectedResource,
248
+ service: statusPageService,
249
+ },
250
+ ])(
251
+ "$name writes a salted scrypt hash instead of SHA-256",
252
+ async ({ resource, service }: ResourceTestCase) => {
253
+ setPlainMasterPassword(resource, "shared master password");
254
+
255
+ await service.hash(resource);
256
+
257
+ expect(resource.masterPasswordSalt).toMatch(/^[0-9a-f]{64}$/);
258
+ expect(resource.masterPassword!.toString()).toMatch(
259
+ /^scrypt\$N=\d+,r=\d+,p=\d+\$[0-9a-f]{64}$/,
260
+ );
261
+ expect(resource.masterPassword!.toString()).not.toBe(
262
+ CryptoJS.SHA256(
263
+ EncryptionSecret.toString() + "shared master password",
264
+ ).toString(),
265
+ );
266
+ await expect(
267
+ PasswordHash.verify({
268
+ plainValue: "shared master password",
269
+ storedValue: resource.masterPassword!.toString(),
270
+ salt: resource.masterPasswordSalt,
271
+ }),
272
+ ).resolves.toBe(true);
273
+ },
274
+ );
275
+
276
+ test.each([
277
+ {
278
+ name: "Dashboard",
279
+ first: new Dashboard() as ProtectedResource,
280
+ second: new Dashboard() as ProtectedResource,
281
+ service: dashboardService,
282
+ },
283
+ {
284
+ name: "StatusPage",
285
+ first: new StatusPage() as ProtectedResource,
286
+ second: new StatusPage() as ProtectedResource,
287
+ service: statusPageService,
288
+ },
289
+ ])(
290
+ "two $name resources with the same password get unique salts and hashes",
291
+ async ({ first, second, service }: ResourcePairTestCase) => {
292
+ setPlainMasterPassword(first, "same password");
293
+ setPlainMasterPassword(second, "same password");
294
+
295
+ await service.hash(first);
296
+ await service.hash(second);
297
+
298
+ expect(first.masterPasswordSalt).not.toBe(second.masterPasswordSalt);
299
+ expect(first.masterPassword!.toString()).not.toBe(
300
+ second.masterPassword!.toString(),
301
+ );
302
+ },
303
+ );
304
+ });
305
+
306
+ describe("Master-password update path", () => {
307
+ test.each(resourceCases)(
308
+ "$name password updates write a new scrypt hash and salt together",
309
+ async ({ service }: ResourceCase) => {
310
+ const result: JSONObject = await service.sanitizeCreateOrUpdate(
311
+ { masterPassword: new HashedString("replacement password") },
312
+ { isRoot: true },
313
+ true,
314
+ );
315
+
316
+ expect(result["masterPasswordSalt"]).toMatch(/^[0-9a-f]{64}$/);
317
+ expect(result["masterPassword"]).toMatch(/^scrypt\$/);
318
+ await expect(
319
+ PasswordHash.verify({
320
+ plainValue: "replacement password",
321
+ storedValue: result["masterPassword"] as string,
322
+ salt: result["masterPasswordSalt"] as string,
323
+ }),
324
+ ).resolves.toBe(true);
325
+ },
326
+ );
327
+
328
+ test.each(resourceCases)(
329
+ "$name unrelated updates do not rotate the master-password salt",
330
+ async ({ service }: ResourceCase) => {
331
+ const result: JSONObject = await service.sanitizeCreateOrUpdate(
332
+ { name: "Renamed resource" },
333
+ { isRoot: true },
334
+ true,
335
+ );
336
+
337
+ expect(result["masterPasswordSalt"]).toBeUndefined();
338
+ expect(Object.keys(result)).not.toContain("masterPasswordSalt");
339
+ },
340
+ );
341
+
342
+ test.each(resourceCases)(
343
+ "$name refuses a bare pre-hashed string that could desynchronise hash and salt",
344
+ async ({ service }: ResourceCase) => {
345
+ await expect(
346
+ service.sanitizeCreateOrUpdate(
347
+ { masterPassword: "pre-hashed-value" },
348
+ { isRoot: true },
349
+ true,
350
+ ),
351
+ ).rejects.toThrow("must be supplied as a HashedString");
352
+ },
353
+ );
354
+ });
355
+
356
+ describe("Master-password verification", () => {
357
+ test.each([
358
+ {
359
+ name: "Dashboard",
360
+ resource: new Dashboard() as ProtectedResource,
361
+ service: dashboardService,
362
+ },
363
+ {
364
+ name: "StatusPage",
365
+ resource: new StatusPage() as ProtectedResource,
366
+ service: statusPageService,
367
+ },
368
+ ])(
369
+ "$name accepts the right scrypt password and rejects the wrong one without rewriting",
370
+ async ({ resource, service }: ResourceTestCase) => {
371
+ await makeCurrentResource({ resource, password: "correct password" });
372
+ const update: jest.SpiedFunction<
373
+ ResourceService["updateColumnsByIdWithoutHooks"]
374
+ > = jest
375
+ .spyOn(service, "updateColumnsByIdWithoutHooks")
376
+ .mockResolvedValue(undefined);
377
+
378
+ await expect(
379
+ service.verifyHashedColumnValue({
380
+ item: resource,
381
+ columnName: "masterPassword",
382
+ plainValue: "correct password",
383
+ }),
384
+ ).resolves.toBe(true);
385
+ await expect(
386
+ service.verifyHashedColumnValue({
387
+ item: resource,
388
+ columnName: "masterPassword",
389
+ plainValue: "wrong password",
390
+ }),
391
+ ).resolves.toBe(false);
392
+
393
+ expect(update).not.toHaveBeenCalled();
394
+ },
395
+ );
396
+
397
+ test.each([
398
+ {
399
+ name: "Dashboard",
400
+ resource: new Dashboard() as ProtectedResource,
401
+ service: dashboardService,
402
+ },
403
+ {
404
+ name: "StatusPage",
405
+ resource: new StatusPage() as ProtectedResource,
406
+ service: statusPageService,
407
+ },
408
+ ])(
409
+ "$name transparently upgrades a valid legacy SHA-256 password",
410
+ async ({ resource, service }: ResourceTestCase) => {
411
+ const legacyHash: string = await HashedString.hashValue(
412
+ "legacy password",
413
+ EncryptionSecret,
414
+ );
415
+ setStoredMasterPassword(resource, legacyHash);
416
+
417
+ const update: jest.SpiedFunction<
418
+ ResourceService["updateColumnsByIdWithoutHooks"]
419
+ > = jest
420
+ .spyOn(service, "updateColumnsByIdWithoutHooks")
421
+ .mockResolvedValue(undefined);
422
+
423
+ await expect(
424
+ service.verifyHashedColumnValue({
425
+ item: resource,
426
+ columnName: "masterPassword",
427
+ plainValue: "legacy password",
428
+ }),
429
+ ).resolves.toBe(true);
430
+
431
+ expect(update).toHaveBeenCalledTimes(1);
432
+ const write: UpdateColumnsInput = update.mock.calls[0]![0];
433
+
434
+ expect(write.id.toString()).toBe(resource._id);
435
+ expect(write.skipUpdateDateColumn).toBe(true);
436
+ expect(write.expectedData).toEqual({
437
+ masterPassword: legacyHash,
438
+ masterPasswordSalt: null,
439
+ });
440
+ expect(write.data["masterPasswordSalt"]).toMatch(/^[0-9a-f]{64}$/);
441
+ expect(write.data["masterPassword"]).toMatch(/^scrypt\$/);
442
+ await expect(
443
+ PasswordHash.verify({
444
+ plainValue: "legacy password",
445
+ storedValue: write.data["masterPassword"] as string,
446
+ salt: write.data["masterPasswordSalt"] as string,
447
+ }),
448
+ ).resolves.toBe(true);
449
+ },
450
+ );
451
+
452
+ test.each([
453
+ {
454
+ name: "Dashboard",
455
+ resource: new Dashboard() as ProtectedResource,
456
+ service: dashboardService,
457
+ },
458
+ {
459
+ name: "StatusPage",
460
+ resource: new StatusPage() as ProtectedResource,
461
+ service: statusPageService,
462
+ },
463
+ ])(
464
+ "$name repairs a bare legacy hash left beside a stale salt by an old pod",
465
+ async ({ resource, service }: ResourceTestCase) => {
466
+ const legacyHash: string = await HashedString.hashValue(
467
+ "rolling password",
468
+ EncryptionSecret,
469
+ );
470
+ const staleSalt: string = PasswordHash.generateSalt();
471
+ setStoredMasterPassword(resource, legacyHash, staleSalt);
472
+
473
+ const update: jest.SpiedFunction<
474
+ ResourceService["updateColumnsByIdWithoutHooks"]
475
+ > = jest
476
+ .spyOn(service, "updateColumnsByIdWithoutHooks")
477
+ .mockResolvedValue(undefined);
478
+
479
+ await expect(
480
+ service.verifyHashedColumnValue({
481
+ item: resource,
482
+ columnName: "masterPassword",
483
+ plainValue: "rolling password",
484
+ }),
485
+ ).resolves.toBe(true);
486
+
487
+ expect(update).toHaveBeenCalledTimes(1);
488
+ const write: UpdateColumnsInput = update.mock.calls[0]![0];
489
+
490
+ expect(write.expectedData).toEqual({
491
+ masterPassword: legacyHash,
492
+ masterPasswordSalt: staleSalt,
493
+ });
494
+ expect(write.data["masterPassword"]).toMatch(/^scrypt\$/);
495
+ expect(write.data["masterPasswordSalt"]).not.toBe(staleSalt);
496
+ },
497
+ );
498
+
499
+ test.each([
500
+ {
501
+ name: "Dashboard",
502
+ resource: new Dashboard() as ProtectedResource,
503
+ service: dashboardService,
504
+ },
505
+ {
506
+ name: "StatusPage",
507
+ resource: new StatusPage() as ProtectedResource,
508
+ service: statusPageService,
509
+ },
510
+ ])(
511
+ "$name never upgrades a legacy hash after a wrong password",
512
+ async ({ resource, service }: ResourceTestCase) => {
513
+ setStoredMasterPassword(
514
+ resource,
515
+ await HashedString.hashValue("legacy password", EncryptionSecret),
516
+ );
517
+
518
+ const update: jest.SpiedFunction<
519
+ ResourceService["updateColumnsByIdWithoutHooks"]
520
+ > = jest
521
+ .spyOn(service, "updateColumnsByIdWithoutHooks")
522
+ .mockResolvedValue(undefined);
523
+
524
+ await expect(
525
+ service.verifyHashedColumnValue({
526
+ item: resource,
527
+ columnName: "masterPassword",
528
+ plainValue: "wrong password",
529
+ }),
530
+ ).resolves.toBe(false);
531
+ expect(update).not.toHaveBeenCalled();
532
+ },
533
+ );
534
+
535
+ test.each([
536
+ {
537
+ name: "Dashboard",
538
+ resource: new Dashboard() as ProtectedResource,
539
+ service: dashboardService,
540
+ },
541
+ {
542
+ name: "StatusPage",
543
+ resource: new StatusPage() as ProtectedResource,
544
+ service: statusPageService,
545
+ },
546
+ ])(
547
+ "$name fails closed when a scrypt hash is read without its salt",
548
+ async ({ resource, service }: ResourceTestCase) => {
549
+ const salt: string = PasswordHash.generateSalt();
550
+ setStoredMasterPassword(
551
+ resource,
552
+ await PasswordHash.hash({
553
+ plainValue: "correct password",
554
+ salt,
555
+ }),
556
+ );
557
+
558
+ await expect(
559
+ service.verifyHashedColumnValue({
560
+ item: resource,
561
+ columnName: "masterPassword",
562
+ plainValue: "correct password",
563
+ }),
564
+ ).rejects.toBeInstanceOf(BadDataException);
565
+ },
566
+ );
567
+ });