@oneuptime/common 12.0.2 → 12.0.4

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 (384) hide show
  1. package/Models/DatabaseModels/CephCluster.ts +14 -0
  2. package/Models/DatabaseModels/CodeRepository.ts +14 -6
  3. package/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.ts +14 -0
  4. package/Models/DatabaseModels/DockerHost.ts +14 -0
  5. package/Models/DatabaseModels/GlobalOidcProject.ts +18 -0
  6. package/Models/DatabaseModels/GlobalSsoProject.ts +18 -0
  7. package/Models/DatabaseModels/Index.ts +2 -0
  8. package/Models/DatabaseModels/IoTFleet.ts +14 -0
  9. package/Models/DatabaseModels/KubernetesCluster.ts +14 -0
  10. package/Models/DatabaseModels/LogDropFilter.ts +9 -20
  11. package/Models/DatabaseModels/NetworkDevice.ts +14 -0
  12. package/Models/DatabaseModels/NetworkInterface.ts +18 -0
  13. package/Models/DatabaseModels/Project.ts +16 -2
  14. package/Models/DatabaseModels/ProxmoxCluster.ts +14 -0
  15. package/Models/DatabaseModels/Service.ts +30 -0
  16. package/Models/DatabaseModels/StatusPageMonitorRule.ts +911 -0
  17. package/Models/DatabaseModels/StatusPagePrivateUser.ts +30 -0
  18. package/Models/DatabaseModels/StatusPageResource.ts +73 -0
  19. package/Models/DatabaseModels/TraceDropFilter.ts +9 -20
  20. package/Models/DatabaseModels/User.ts +30 -0
  21. package/Server/API/AIAgentDataAPI.ts +31 -0
  22. package/Server/API/BaseAPI.ts +13 -2
  23. package/Server/API/DashboardAPI.ts +458 -12
  24. package/Server/API/GitHubAPI.ts +119 -283
  25. package/Server/API/UserAPI.ts +263 -1
  26. package/Server/EnvironmentConfig.ts +38 -4
  27. package/Server/Infrastructure/GlobalCache.ts +152 -0
  28. package/Server/Infrastructure/Postgres/DataSourceOptions.ts +16 -0
  29. package/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.ts +123 -0
  30. package/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.ts +21 -0
  31. package/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.ts +68 -0
  32. package/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.ts +100 -0
  33. package/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.ts +214 -0
  34. package/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.ts +69 -0
  35. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +12 -0
  36. package/Server/Services/AlertStateTimelineService.ts +20 -0
  37. package/Server/Services/AnalyticsDatabaseService.ts +72 -1
  38. package/Server/Services/CephClusterService.ts +26 -86
  39. package/Server/Services/CloudResourceService.ts +23 -83
  40. package/Server/Services/CodeRepositoryService.ts +105 -2
  41. package/Server/Services/DatabaseService.ts +296 -2
  42. package/Server/Services/DockerHostService.ts +19 -79
  43. package/Server/Services/DockerSwarmClusterService.ts +27 -88
  44. package/Server/Services/HostService.ts +46 -99
  45. package/Server/Services/IncidentStateTimelineService.ts +20 -0
  46. package/Server/Services/Index.ts +2 -0
  47. package/Server/Services/IoTFleetService.ts +19 -79
  48. package/Server/Services/KubernetesClusterService.ts +17 -78
  49. package/Server/Services/LabelService.ts +57 -10
  50. package/Server/Services/MetricTypeService.ts +84 -0
  51. package/Server/Services/MonitorService.ts +89 -14
  52. package/Server/Services/MonitorStatusTimelineService.ts +29 -0
  53. package/Server/Services/MonitorTemplateService.ts +65 -0
  54. package/Server/Services/OpenTelemetryIngestService.ts +230 -26
  55. package/Server/Services/PodmanHostService.ts +19 -79
  56. package/Server/Services/ProxmoxClusterService.ts +23 -83
  57. package/Server/Services/RumApplicationService.ts +19 -80
  58. package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +21 -0
  59. package/Server/Services/ServerlessFunctionService.ts +24 -85
  60. package/Server/Services/ServiceService.ts +299 -73
  61. package/Server/Services/StatusPageMonitorRuleEngineService.ts +752 -0
  62. package/Server/Services/StatusPageMonitorRuleService.ts +471 -0
  63. package/Server/Services/TelemetryEntityRelationshipService.ts +1 -0
  64. package/Server/Services/TelemetryEntityService.ts +1 -0
  65. package/Server/Services/UserService.ts +184 -0
  66. package/Server/Utils/AI/Toolbox/CodeTools.ts +45 -4
  67. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +13 -0
  68. package/Server/Utils/CodeRepository/GitHub/GitHub.ts +157 -4
  69. package/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.ts +130 -0
  70. package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +170 -39
  71. package/Server/Utils/Monitor/MonitorResource.ts +63 -36
  72. package/Server/Utils/Monitor/MonitorStatusTimeline.ts +36 -0
  73. package/Server/Utils/Monitor/MonitorStepsProjectValidator.ts +148 -127
  74. package/Server/Utils/Monitor/MonitorStepsReferenceExtractor.ts +434 -0
  75. package/Server/Utils/PasswordHash.ts +306 -0
  76. package/Server/Utils/SingleFlight.ts +83 -0
  77. package/Server/Utils/StatusPage/MonitorRulePatternValidator.ts +46 -0
  78. package/Server/Utils/Telemetry/EntityRegistry.ts +107 -26
  79. package/Server/Utils/Telemetry/ResourceHeartbeat.ts +314 -0
  80. package/Server/Utils/Telemetry/Telemetry.ts +351 -122
  81. package/Tests/App/Dashboard/OverviewCustomFields.test.tsx +333 -0
  82. package/Tests/App/Dashboard/UserCustomFields.test.tsx +392 -0
  83. package/Tests/App/Dashboard/WidgetCatalog.test.ts +2 -0
  84. package/Tests/Models/DatabaseModels/ColumnTransformerInvariants.test.ts +306 -0
  85. package/Tests/Models/DatabaseModels/NumberColumnDeserialization.test.ts +333 -0
  86. package/Tests/Models/DatabaseModels/StatusPageMonitorRuleModel.test.ts +190 -0
  87. package/Tests/Server/API/AIAgentDataRepositoryToken.test.ts +381 -0
  88. package/Tests/Server/API/DashboardPublicAttributeValuesAPI.test.ts +943 -0
  89. package/Tests/Server/API/DashboardPublicMetricsAggregateAPI.test.ts +1296 -0
  90. package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +26 -0
  91. package/Tests/Server/API/DashboardPublicTemplatePayloads.test.ts +598 -0
  92. package/Tests/Server/API/GitHubAppInstallationBindingAPI.test.ts +539 -0
  93. package/Tests/Server/API/Helpers.ts +6 -1
  94. package/Tests/Server/API/UserProjectsAPI.test.ts +852 -0
  95. package/Tests/Server/Infrastructure/GlobalCache.test.ts +354 -0
  96. package/Tests/Server/Infrastructure/PostgresLockTimeoutOptions.test.ts +181 -0
  97. package/Tests/Server/Infrastructure/SemaphoreMutex.test.ts +215 -0
  98. package/Tests/Server/Services/AddPerUserPasswordSaltMigration.test.ts +178 -0
  99. package/Tests/Server/Services/AnalyticsDatabasePaginationStability.test.ts +582 -0
  100. package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +5 -0
  101. package/Tests/Server/Services/CodeRepositoryInstallationBinding.test.ts +305 -0
  102. package/Tests/Server/Services/CodeRepositoryResolutionBinding.test.ts +147 -0
  103. package/Tests/Server/Services/DatabaseServicePerUserPasswordSalt.test.ts +870 -0
  104. package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlocked.test.ts +342 -0
  105. package/Tests/Server/Services/DropFilterSaveValidation.test.ts +245 -0
  106. package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +163 -15
  107. package/Tests/Server/Services/MetricTypeAttachServices.test.ts +222 -0
  108. package/Tests/Server/Services/MissingReferenceWriteGuard.test.ts +614 -0
  109. package/Tests/Server/Services/MonitorStatusTimelineFastPath.test.ts +23 -0
  110. package/Tests/Server/Services/OpenTelemetryServiceResolutionCache.test.ts +568 -0
  111. package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +305 -56
  112. package/Tests/Server/Services/RestoreDroppedUniqueIndexesMigration.test.ts +386 -0
  113. package/Tests/Server/Services/RestoreServiceLowerNameIndexMigration.test.ts +240 -0
  114. package/Tests/Server/Services/ServiceUpdateLastSeenSplitGates.test.ts +705 -0
  115. package/Tests/Server/Services/StatusPageMonitorRuleEngineService.test.ts +1719 -0
  116. package/Tests/Server/Services/StatusPageMonitorRuleHooks.test.ts +1212 -0
  117. package/Tests/Server/Services/StatusPageMonitorRuleMonitorCreateHook.test.ts +228 -0
  118. package/Tests/Server/Services/UserServiceFirstMasterAdminElection.test.ts +885 -0
  119. package/Tests/Server/Services/WidenHashedStringColumnsForScryptMigration.test.ts +220 -0
  120. package/Tests/Server/Utils/AI/CodeTools.test.ts +47 -1
  121. package/Tests/Server/Utils/AI/CodeWriteTools.test.ts +10 -0
  122. package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +267 -0
  123. package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +120 -0
  124. package/Tests/Server/Utils/CodeRepository/GitHubInstallationBinding.test.ts +292 -0
  125. package/Tests/Server/Utils/Database/ProjectScopedReferenceValidator.test.ts +260 -13
  126. package/Tests/Server/Utils/EntityRegistryRowFence.test.ts +302 -0
  127. package/Tests/Server/Utils/GitHubInstallationOwnershipVerification.test.ts +340 -0
  128. package/Tests/Server/Utils/GitHubWebhookAndTreeCacheIsolation.test.ts +250 -0
  129. package/Tests/Server/Utils/MetricTypeIndexConvergence.test.ts +598 -0
  130. package/Tests/Server/Utils/Monitor/MonitorStatusTimelineUnusableStatus.test.ts +154 -0
  131. package/Tests/Server/Utils/Monitor/MonitorStepsProjectValidator.test.ts +638 -92
  132. package/Tests/Server/Utils/Monitor/MonitorStepsReferenceExtractor.test.ts +724 -0
  133. package/Tests/Server/Utils/PasswordHash.test.ts +555 -0
  134. package/Tests/Server/Utils/SingleFlight.test.ts +221 -0
  135. package/Tests/Types/Dashboard/NetworkDashboardTemplate.test.ts +793 -0
  136. package/Tests/Types/Database/BigIntColumnTransformer.test.ts +147 -0
  137. package/Tests/Types/Database/ColumnLength.test.ts +7 -1
  138. package/Tests/Types/Database/DatabasePropertyTransformer.test.ts +140 -0
  139. package/Tests/Types/Database/NumericColumnValue.test.ts +127 -0
  140. package/Tests/Types/Decimal.test.ts +37 -0
  141. package/Tests/Types/HashedStringPerUserSalt.test.ts +476 -0
  142. package/Tests/Types/Monitor/DockerAlertTemplates.test.ts +293 -0
  143. package/Tests/Types/Monitor/HostAlertTemplates.test.ts +249 -0
  144. package/Tests/Types/Monitor/IotAlertTemplates.test.ts +320 -0
  145. package/Tests/Types/Monitor/PodmanAlertTemplates.test.ts +271 -0
  146. package/Tests/Types/Monitor/SnmpVersionParsing.test.ts +152 -0
  147. package/Tests/Types/Port.test.ts +38 -0
  148. package/Tests/UI/Components/CustomFields/CustomFieldsDetail.test.tsx +696 -0
  149. package/Tests/UI/Components/LogsHistogram.test.tsx +276 -0
  150. package/Tests/UI/Components/UseLiveLogsRefresh.test.tsx +489 -0
  151. package/Tests/UI/Components/UseLogsHistogram.test.tsx +500 -0
  152. package/Tests/UI/Utils/UserProjectsModelAPI.test.ts +714 -0
  153. package/Tests/Utils/Dashboard/Components/DashboardComponentsUtil.test.ts +120 -0
  154. package/Tests/Utils/Dashboard/DashboardNetworkMapComponent.test.ts +327 -0
  155. package/Tests/Utils/TeamMembersByProject.test.ts +655 -0
  156. package/Types/Dashboard/DashboardComponentType.ts +1 -0
  157. package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
  158. package/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.ts +40 -0
  159. package/Types/Dashboard/DashboardTemplates.ts +397 -0
  160. package/Types/Database/BigIntColumnTransformer.ts +74 -0
  161. package/Types/Database/ColumnLength.ts +9 -1
  162. package/Types/Database/DatabaseProperty.ts +26 -0
  163. package/Types/Database/NumericColumnValue.ts +128 -0
  164. package/Types/Database/TableColumn.ts +11 -0
  165. package/Types/Database/UnsynchronizedIndex.ts +48 -0
  166. package/Types/Decimal.ts +16 -4
  167. package/Types/HashedString.ts +139 -4
  168. package/Types/Permission.ts +48 -0
  169. package/Types/Port.ts +16 -4
  170. package/UI/Components/CustomFields/CustomFieldsDetail.tsx +85 -11
  171. package/UI/Components/LogsViewer/useLiveLogsRefresh.ts +86 -0
  172. package/UI/Components/LogsViewer/useLogsHistogram.ts +113 -0
  173. package/UI/Components/Navbar/NavBar.tsx +12 -2
  174. package/UI/Components/Navbar/NavBarMenuModal.tsx +19 -7
  175. package/UI/Utils/ModelAPI/UserProjectsModelAPI.ts +257 -0
  176. package/Utils/Dashboard/Components/DashboardNetworkMapComponent.ts +136 -0
  177. package/Utils/Dashboard/Components/Index.ts +7 -0
  178. package/Utils/TeamMembersByProject.ts +237 -0
  179. package/build/dist/Models/DatabaseModels/CephCluster.js +16 -1
  180. package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -1
  181. package/build/dist/Models/DatabaseModels/CodeRepository.js +14 -6
  182. package/build/dist/Models/DatabaseModels/CodeRepository.js.map +1 -1
  183. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +12 -0
  184. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
  185. package/build/dist/Models/DatabaseModels/DockerHost.js +16 -1
  186. package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
  187. package/build/dist/Models/DatabaseModels/GlobalOidcProject.js +16 -1
  188. package/build/dist/Models/DatabaseModels/GlobalOidcProject.js.map +1 -1
  189. package/build/dist/Models/DatabaseModels/GlobalSsoProject.js +16 -1
  190. package/build/dist/Models/DatabaseModels/GlobalSsoProject.js.map +1 -1
  191. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  192. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  193. package/build/dist/Models/DatabaseModels/IoTFleet.js +16 -1
  194. package/build/dist/Models/DatabaseModels/IoTFleet.js.map +1 -1
  195. package/build/dist/Models/DatabaseModels/KubernetesCluster.js +16 -1
  196. package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
  197. package/build/dist/Models/DatabaseModels/LogDropFilter.js +9 -20
  198. package/build/dist/Models/DatabaseModels/LogDropFilter.js.map +1 -1
  199. package/build/dist/Models/DatabaseModels/NetworkDevice.js +16 -1
  200. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  201. package/build/dist/Models/DatabaseModels/NetworkInterface.js +16 -1
  202. package/build/dist/Models/DatabaseModels/NetworkInterface.js.map +1 -1
  203. package/build/dist/Models/DatabaseModels/Project.js +16 -2
  204. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  205. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +16 -1
  206. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -1
  207. package/build/dist/Models/DatabaseModels/Service.js +28 -1
  208. package/build/dist/Models/DatabaseModels/Service.js.map +1 -1
  209. package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js +925 -0
  210. package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js.map +1 -0
  211. package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js +32 -0
  212. package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js.map +1 -1
  213. package/build/dist/Models/DatabaseModels/StatusPageResource.js +72 -0
  214. package/build/dist/Models/DatabaseModels/StatusPageResource.js.map +1 -1
  215. package/build/dist/Models/DatabaseModels/TraceDropFilter.js +9 -20
  216. package/build/dist/Models/DatabaseModels/TraceDropFilter.js.map +1 -1
  217. package/build/dist/Models/DatabaseModels/User.js +32 -0
  218. package/build/dist/Models/DatabaseModels/User.js.map +1 -1
  219. package/build/dist/Server/API/AIAgentDataAPI.js +21 -2
  220. package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
  221. package/build/dist/Server/API/BaseAPI.js +11 -1
  222. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  223. package/build/dist/Server/API/DashboardAPI.js +343 -10
  224. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  225. package/build/dist/Server/API/GitHubAPI.js +94 -167
  226. package/build/dist/Server/API/GitHubAPI.js.map +1 -1
  227. package/build/dist/Server/API/UserAPI.js +187 -2
  228. package/build/dist/Server/API/UserAPI.js.map +1 -1
  229. package/build/dist/Server/EnvironmentConfig.js +34 -4
  230. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  231. package/build/dist/Server/Infrastructure/GlobalCache.js +120 -0
  232. package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
  233. package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js +5 -3
  234. package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js.map +1 -1
  235. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.js +48 -0
  236. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.js.map +1 -0
  237. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.js +14 -0
  238. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.js.map +1 -0
  239. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.js +59 -0
  240. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.js.map +1 -0
  241. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js +93 -0
  242. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js.map +1 -0
  243. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.js +101 -0
  244. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.js.map +1 -0
  245. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.js +66 -0
  246. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.js.map +1 -0
  247. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +12 -0
  248. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  249. package/build/dist/Server/Services/AlertStateTimelineService.js +19 -0
  250. package/build/dist/Server/Services/AlertStateTimelineService.js.map +1 -1
  251. package/build/dist/Server/Services/AnalyticsDatabaseService.js +57 -1
  252. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  253. package/build/dist/Server/Services/CephClusterService.js +26 -76
  254. package/build/dist/Server/Services/CephClusterService.js.map +1 -1
  255. package/build/dist/Server/Services/CloudResourceService.js +23 -73
  256. package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
  257. package/build/dist/Server/Services/CodeRepositoryService.js +90 -2
  258. package/build/dist/Server/Services/CodeRepositoryService.js.map +1 -1
  259. package/build/dist/Server/Services/DatabaseService.js +246 -2
  260. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  261. package/build/dist/Server/Services/DockerHostService.js +19 -69
  262. package/build/dist/Server/Services/DockerHostService.js.map +1 -1
  263. package/build/dist/Server/Services/DockerSwarmClusterService.js +27 -78
  264. package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
  265. package/build/dist/Server/Services/HostService.js +58 -102
  266. package/build/dist/Server/Services/HostService.js.map +1 -1
  267. package/build/dist/Server/Services/IncidentStateTimelineService.js +19 -0
  268. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  269. package/build/dist/Server/Services/Index.js +2 -0
  270. package/build/dist/Server/Services/Index.js.map +1 -1
  271. package/build/dist/Server/Services/IoTFleetService.js +19 -69
  272. package/build/dist/Server/Services/IoTFleetService.js.map +1 -1
  273. package/build/dist/Server/Services/KubernetesClusterService.js +17 -68
  274. package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
  275. package/build/dist/Server/Services/LabelService.js +42 -7
  276. package/build/dist/Server/Services/LabelService.js.map +1 -1
  277. package/build/dist/Server/Services/MetricTypeService.js +74 -0
  278. package/build/dist/Server/Services/MetricTypeService.js.map +1 -1
  279. package/build/dist/Server/Services/MonitorService.js +81 -9
  280. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  281. package/build/dist/Server/Services/MonitorStatusTimelineService.js +28 -0
  282. package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
  283. package/build/dist/Server/Services/MonitorTemplateService.js +60 -0
  284. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  285. package/build/dist/Server/Services/OpenTelemetryIngestService.js +155 -25
  286. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  287. package/build/dist/Server/Services/PodmanHostService.js +19 -69
  288. package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
  289. package/build/dist/Server/Services/ProxmoxClusterService.js +23 -73
  290. package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
  291. package/build/dist/Server/Services/RumApplicationService.js +19 -70
  292. package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
  293. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +20 -0
  294. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
  295. package/build/dist/Server/Services/ServerlessFunctionService.js +24 -75
  296. package/build/dist/Server/Services/ServerlessFunctionService.js.map +1 -1
  297. package/build/dist/Server/Services/ServiceService.js +236 -61
  298. package/build/dist/Server/Services/ServiceService.js.map +1 -1
  299. package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js +603 -0
  300. package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js.map +1 -0
  301. package/build/dist/Server/Services/StatusPageMonitorRuleService.js +401 -0
  302. package/build/dist/Server/Services/StatusPageMonitorRuleService.js.map +1 -0
  303. package/build/dist/Server/Services/TelemetryEntityRelationshipService.js +1 -0
  304. package/build/dist/Server/Services/TelemetryEntityRelationshipService.js.map +1 -1
  305. package/build/dist/Server/Services/TelemetryEntityService.js +1 -0
  306. package/build/dist/Server/Services/TelemetryEntityService.js.map +1 -1
  307. package/build/dist/Server/Services/UserService.js +165 -0
  308. package/build/dist/Server/Services/UserService.js.map +1 -1
  309. package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js +31 -1
  310. package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js.map +1 -1
  311. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +13 -0
  312. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
  313. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js +126 -4
  314. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js.map +1 -1
  315. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.js +136 -0
  316. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.js.map +1 -0
  317. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +105 -29
  318. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
  319. package/build/dist/Server/Utils/Monitor/MonitorResource.js +56 -32
  320. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  321. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js +30 -3
  322. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js.map +1 -1
  323. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js +110 -98
  324. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js.map +1 -1
  325. package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js +312 -0
  326. package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js.map +1 -0
  327. package/build/dist/Server/Utils/PasswordHash.js +237 -0
  328. package/build/dist/Server/Utils/PasswordHash.js.map +1 -0
  329. package/build/dist/Server/Utils/SingleFlight.js +76 -0
  330. package/build/dist/Server/Utils/SingleFlight.js.map +1 -0
  331. package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js +29 -0
  332. package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js.map +1 -0
  333. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +78 -19
  334. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
  335. package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js +160 -0
  336. package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js.map +1 -0
  337. package/build/dist/Server/Utils/Telemetry/Telemetry.js +279 -104
  338. package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
  339. package/build/dist/Types/Dashboard/DashboardComponentType.js +1 -0
  340. package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
  341. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
  342. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
  343. package/build/dist/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.js +2 -0
  344. package/build/dist/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.js.map +1 -0
  345. package/build/dist/Types/Dashboard/DashboardTemplates.js +368 -0
  346. package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
  347. package/build/dist/Types/Database/BigIntColumnTransformer.js +66 -0
  348. package/build/dist/Types/Database/BigIntColumnTransformer.js.map +1 -0
  349. package/build/dist/Types/Database/ColumnLength.js +9 -1
  350. package/build/dist/Types/Database/ColumnLength.js.map +1 -1
  351. package/build/dist/Types/Database/DatabaseProperty.js +25 -0
  352. package/build/dist/Types/Database/DatabaseProperty.js.map +1 -1
  353. package/build/dist/Types/Database/NumericColumnValue.js +97 -0
  354. package/build/dist/Types/Database/NumericColumnValue.js.map +1 -0
  355. package/build/dist/Types/Database/TableColumn.js.map +1 -1
  356. package/build/dist/Types/Database/UnsynchronizedIndex.js +46 -0
  357. package/build/dist/Types/Database/UnsynchronizedIndex.js.map +1 -0
  358. package/build/dist/Types/Decimal.js +14 -3
  359. package/build/dist/Types/Decimal.js.map +1 -1
  360. package/build/dist/Types/HashedString.js +95 -5
  361. package/build/dist/Types/HashedString.js.map +1 -1
  362. package/build/dist/Types/Permission.js +42 -0
  363. package/build/dist/Types/Permission.js.map +1 -1
  364. package/build/dist/Types/Port.js +15 -5
  365. package/build/dist/Types/Port.js.map +1 -1
  366. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +51 -10
  367. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
  368. package/build/dist/UI/Components/LogsViewer/useLiveLogsRefresh.js +44 -0
  369. package/build/dist/UI/Components/LogsViewer/useLiveLogsRefresh.js.map +1 -0
  370. package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js +75 -0
  371. package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js.map +1 -0
  372. package/build/dist/UI/Components/Navbar/NavBar.js +3 -1
  373. package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
  374. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +19 -7
  375. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
  376. package/build/dist/UI/Utils/ModelAPI/UserProjectsModelAPI.js +156 -0
  377. package/build/dist/UI/Utils/ModelAPI/UserProjectsModelAPI.js.map +1 -0
  378. package/build/dist/Utils/Dashboard/Components/DashboardNetworkMapComponent.js +114 -0
  379. package/build/dist/Utils/Dashboard/Components/DashboardNetworkMapComponent.js.map +1 -0
  380. package/build/dist/Utils/Dashboard/Components/Index.js +4 -0
  381. package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
  382. package/build/dist/Utils/TeamMembersByProject.js +145 -0
  383. package/build/dist/Utils/TeamMembersByProject.js.map +1 -0
  384. package/package.json +1 -1
@@ -0,0 +1,555 @@
1
+ import PasswordHash, { ScryptParams } from "../../../Server/Utils/PasswordHash";
2
+ import { EncryptionSecret } from "../../../Server/EnvironmentConfig";
3
+ import ColumnLength from "../../../Types/Database/ColumnLength";
4
+ import HashedString from "../../../Types/HashedString";
5
+ import BadDataException from "../../../Types/Exception/BadDataException";
6
+ import crypto from "crypto";
7
+ import { describe, expect, test } from "@jest/globals";
8
+
9
+ /*
10
+ * Password hashing with scrypt.
11
+ *
12
+ * A per-user salt stops one attacker's work being reused across accounts, but
13
+ * it does nothing to slow down a single guess — SHA-256 is a few nanoseconds,
14
+ * so a leaked User table used to be a dictionary attack that finished over
15
+ * lunch. scrypt makes each guess cost real time AND real memory, which is the
16
+ * half that GPUs and custom silicon cannot buy their way out of.
17
+ *
18
+ * These tests pin four things:
19
+ *
20
+ * 1. the construction is exactly scrypt over an HMAC-peppered password,
21
+ * recomputed here from primitives rather than read back off the
22
+ * implementation;
23
+ * 2. the stored string carries its own cost parameters, which is what makes
24
+ * raising the cost later a one-line change instead of a migration;
25
+ * 3. every earlier scheme still verifies, or the deploy locks everyone out;
26
+ * 4. needsUpgrade() reports precisely the hashes that are not what we write
27
+ * today — that predicate IS the migration.
28
+ */
29
+
30
+ const SALT: string =
31
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
32
+
33
+ type ScryptFunction = (data: {
34
+ plainValue: string;
35
+ salt: string;
36
+ params?: ScryptParams;
37
+ }) => string;
38
+
39
+ /*
40
+ * The construction, spelled out from Node primitives. Deliberately NOT built
41
+ * on anything in PasswordHash: if the implementation drifts, this has to
42
+ * disagree with it.
43
+ */
44
+ const scryptFromPrimitives: ScryptFunction = (data: {
45
+ plainValue: string;
46
+ salt: string;
47
+ params?: ScryptParams;
48
+ }): string => {
49
+ const params: ScryptParams = data.params || { N: 16384, r: 8, p: 1 };
50
+
51
+ const peppered: Buffer = crypto
52
+ .createHmac("sha256", EncryptionSecret.toString())
53
+ .update(data.plainValue, "utf8")
54
+ .digest();
55
+
56
+ return crypto
57
+ .scryptSync(peppered, data.salt, 32, {
58
+ N: params.N,
59
+ r: params.r,
60
+ p: params.p,
61
+ maxmem: 128 * params.N * params.r * 2,
62
+ })
63
+ .toString("hex");
64
+ };
65
+
66
+ describe("PasswordHash — the stored format", () => {
67
+ test("is algorithm, parameters, then digest", async () => {
68
+ const stored: string = await PasswordHash.hash({
69
+ plainValue: "password",
70
+ salt: SALT,
71
+ });
72
+
73
+ expect(stored).toMatch(/^scrypt\$N=\d+,r=\d+,p=\d+\$[0-9a-f]{64}$/);
74
+ });
75
+
76
+ test("records the parameters actually in use", async () => {
77
+ const params: ScryptParams = PasswordHash.getCurrentParams();
78
+ const stored: string = await PasswordHash.hash({
79
+ plainValue: "password",
80
+ salt: SALT,
81
+ });
82
+
83
+ expect(stored.split("$")[1]).toBe(
84
+ `N=${params.N},r=${params.r},p=${params.p}`,
85
+ );
86
+ });
87
+
88
+ test("fits the column that stores it, with room to raise the cost", async () => {
89
+ const stored: string = await PasswordHash.hash({
90
+ plainValue: "password",
91
+ salt: SALT,
92
+ });
93
+
94
+ expect(stored.length).toBeLessThanOrEqual(ColumnLength.HashedString);
95
+ // Headroom for a much larger N and a longer parameter list later.
96
+ expect(ColumnLength.HashedString - stored.length).toBeGreaterThan(100);
97
+ });
98
+
99
+ test("the cost is high enough to be worth having", () => {
100
+ const params: ScryptParams = PasswordHash.getCurrentParams();
101
+
102
+ /*
103
+ * scrypt's memory cost is 128 * N * r bytes. Anything below a few MiB is
104
+ * not meaningfully harder than a fast hash on attack hardware.
105
+ */
106
+ expect(128 * params.N * params.r).toBeGreaterThanOrEqual(16 * 1024 * 1024);
107
+ // N must be a power of two or scrypt rejects it outright.
108
+ expect(params.N & (params.N - 1)).toBe(0);
109
+ });
110
+ });
111
+
112
+ describe("PasswordHash — the construction", () => {
113
+ test("is scrypt over the peppered password, digit for digit", async () => {
114
+ const stored: string = await PasswordHash.hash({
115
+ plainValue: "correct-horse-battery-staple",
116
+ salt: SALT,
117
+ });
118
+
119
+ expect(stored.split("$")[2]).toBe(
120
+ scryptFromPrimitives({
121
+ plainValue: "correct-horse-battery-staple",
122
+ salt: SALT,
123
+ }),
124
+ );
125
+ });
126
+
127
+ test("the pepper participates — this is not plain scrypt(password, salt)", async () => {
128
+ /*
129
+ * Without the HMAC step, a stolen database would be crackable on its own.
130
+ * The pepper lives in configuration, so the attacker needs that too.
131
+ */
132
+ const stored: string = await PasswordHash.hash({
133
+ plainValue: "password",
134
+ salt: SALT,
135
+ });
136
+
137
+ const withoutPepper: string = crypto
138
+ .scryptSync("password", SALT, 32, {
139
+ N: 16384,
140
+ r: 8,
141
+ p: 1,
142
+ maxmem: 128 * 16384 * 8 * 2,
143
+ })
144
+ .toString("hex");
145
+
146
+ expect(stored.split("$")[2]).not.toBe(withoutPepper);
147
+ });
148
+
149
+ test("is not a SHA-256 digest of anything", async () => {
150
+ const stored: string = await PasswordHash.hash({
151
+ plainValue: "password",
152
+ salt: SALT,
153
+ });
154
+
155
+ const digest: string = stored.split("$")[2] as string;
156
+
157
+ expect(digest).not.toBe(
158
+ HashedString.computeHash("password", EncryptionSecret, SALT),
159
+ );
160
+ expect(digest).not.toBe(
161
+ HashedString.computeHash("password", EncryptionSecret, null),
162
+ );
163
+ });
164
+ });
165
+
166
+ describe("PasswordHash.generateSalt", () => {
167
+ test("returns 64 hex characters", () => {
168
+ expect(PasswordHash.generateSalt()).toMatch(/^[0-9a-f]{64}$/);
169
+ });
170
+
171
+ test("fits the ShortText column that stores it", () => {
172
+ expect(PasswordHash.generateSalt().length).toBeLessThanOrEqual(
173
+ ColumnLength.ShortText,
174
+ );
175
+ });
176
+
177
+ test("never repeats across a large sample", () => {
178
+ const salts: Set<string> = new Set<string>();
179
+
180
+ for (let i: number = 0; i < 5000; i++) {
181
+ salts.add(PasswordHash.generateSalt());
182
+ }
183
+
184
+ expect(salts.size).toBe(5000);
185
+ });
186
+ });
187
+
188
+ describe("PasswordHash.hash", () => {
189
+ test("the same password under two salts gives two different hashes", async () => {
190
+ const first: string = await PasswordHash.hash({
191
+ plainValue: "shared",
192
+ salt: PasswordHash.generateSalt(),
193
+ });
194
+ const second: string = await PasswordHash.hash({
195
+ plainValue: "shared",
196
+ salt: PasswordHash.generateSalt(),
197
+ });
198
+
199
+ expect(first).not.toBe(second);
200
+ });
201
+
202
+ test("the same password under the same salt is stable", async () => {
203
+ expect(await PasswordHash.hash({ plainValue: "p", salt: SALT })).toBe(
204
+ await PasswordHash.hash({ plainValue: "p", salt: SALT }),
205
+ );
206
+ });
207
+
208
+ test("different passwords under the same salt differ", async () => {
209
+ expect(await PasswordHash.hash({ plainValue: "a", salt: SALT })).not.toBe(
210
+ await PasswordHash.hash({ plainValue: "b", salt: SALT }),
211
+ );
212
+ });
213
+
214
+ test("an empty password hashes to an empty string, not to a usable hash", async () => {
215
+ expect(await PasswordHash.hash({ plainValue: "", salt: SALT })).toBe("");
216
+ });
217
+
218
+ test("refuses to hash without a salt", async () => {
219
+ await expect(
220
+ PasswordHash.hash({ plainValue: "password", salt: "" }),
221
+ ).rejects.toThrow(BadDataException);
222
+ });
223
+
224
+ test("handles a very long password", async () => {
225
+ /*
226
+ * The HMAC step normalises the input to 32 bytes before scrypt sees it,
227
+ * so there is no length limit to trip over (unlike bcrypt's 72 bytes).
228
+ */
229
+ const long: string = "x".repeat(10000);
230
+
231
+ const stored: string = await PasswordHash.hash({
232
+ plainValue: long,
233
+ salt: SALT,
234
+ });
235
+
236
+ await expect(
237
+ PasswordHash.verify({
238
+ plainValue: long,
239
+ storedValue: stored,
240
+ salt: SALT,
241
+ }),
242
+ ).resolves.toBe(true);
243
+ });
244
+
245
+ test("handles unicode and does not confuse it with its bytes", async () => {
246
+ const stored: string = await PasswordHash.hash({
247
+ plainValue: "pässwörd–🔐",
248
+ salt: SALT,
249
+ });
250
+
251
+ await expect(
252
+ PasswordHash.verify({
253
+ plainValue: "pässwörd–🔐",
254
+ storedValue: stored,
255
+ salt: SALT,
256
+ }),
257
+ ).resolves.toBe(true);
258
+
259
+ await expect(
260
+ PasswordHash.verify({
261
+ plainValue: "password-🔐",
262
+ storedValue: stored,
263
+ salt: SALT,
264
+ }),
265
+ ).resolves.toBe(false);
266
+ });
267
+ });
268
+
269
+ describe("PasswordHash.verify — scrypt hashes", () => {
270
+ test("accepts the right password", async () => {
271
+ const stored: string = await PasswordHash.hash({
272
+ plainValue: "right",
273
+ salt: SALT,
274
+ });
275
+
276
+ await expect(
277
+ PasswordHash.verify({
278
+ plainValue: "right",
279
+ storedValue: stored,
280
+ salt: SALT,
281
+ }),
282
+ ).resolves.toBe(true);
283
+ });
284
+
285
+ test("rejects the wrong password", async () => {
286
+ const stored: string = await PasswordHash.hash({
287
+ plainValue: "right",
288
+ salt: SALT,
289
+ });
290
+
291
+ await expect(
292
+ PasswordHash.verify({
293
+ plainValue: "wrong",
294
+ storedValue: stored,
295
+ salt: SALT,
296
+ }),
297
+ ).resolves.toBe(false);
298
+ });
299
+
300
+ test("rejects the right password under someone else's salt", async () => {
301
+ const stored: string = await PasswordHash.hash({
302
+ plainValue: "shared",
303
+ salt: PasswordHash.generateSalt(),
304
+ });
305
+
306
+ await expect(
307
+ PasswordHash.verify({
308
+ plainValue: "shared",
309
+ storedValue: stored,
310
+ salt: PasswordHash.generateSalt(),
311
+ }),
312
+ ).resolves.toBe(false);
313
+ });
314
+
315
+ test("throws rather than silently failing when the salt was not selected", async () => {
316
+ /*
317
+ * A missing salt is a bug in the caller's SELECT, not a bad password.
318
+ * Returning false would turn it into "nobody can log in" with no clue why.
319
+ */
320
+ const stored: string = await PasswordHash.hash({
321
+ plainValue: "right",
322
+ salt: SALT,
323
+ });
324
+
325
+ await expect(
326
+ PasswordHash.verify({
327
+ plainValue: "right",
328
+ storedValue: stored,
329
+ salt: null,
330
+ }),
331
+ ).rejects.toThrow(BadDataException);
332
+ });
333
+
334
+ test("verifies a hash written at older cost parameters", async () => {
335
+ /*
336
+ * The whole point of putting the parameters in the hash: raising the cost
337
+ * must not invalidate what is already stored.
338
+ */
339
+ const cheapParams: ScryptParams = { N: 1024, r: 8, p: 1 };
340
+ const stored: string = `scrypt$N=1024,r=8,p=1$${scryptFromPrimitives({
341
+ plainValue: "old-cost",
342
+ salt: SALT,
343
+ params: cheapParams,
344
+ })}`;
345
+
346
+ await expect(
347
+ PasswordHash.verify({
348
+ plainValue: "old-cost",
349
+ storedValue: stored,
350
+ salt: SALT,
351
+ }),
352
+ ).resolves.toBe(true);
353
+
354
+ await expect(
355
+ PasswordHash.verify({
356
+ plainValue: "wrong",
357
+ storedValue: stored,
358
+ salt: SALT,
359
+ }),
360
+ ).resolves.toBe(false);
361
+ });
362
+
363
+ test("does not accept a hash verified at the wrong parameters", async () => {
364
+ // Same password, same salt, different N — must not collide.
365
+ const atCheap: string = scryptFromPrimitives({
366
+ plainValue: "password",
367
+ salt: SALT,
368
+ params: { N: 1024, r: 8, p: 1 },
369
+ });
370
+
371
+ const stored: string = await PasswordHash.hash({
372
+ plainValue: "password",
373
+ salt: SALT,
374
+ });
375
+
376
+ expect(stored.split("$")[2]).not.toBe(atCheap);
377
+ });
378
+
379
+ test("returns false for empty input rather than throwing", async () => {
380
+ await expect(
381
+ PasswordHash.verify({
382
+ plainValue: "",
383
+ storedValue: "anything",
384
+ salt: SALT,
385
+ }),
386
+ ).resolves.toBe(false);
387
+
388
+ await expect(
389
+ PasswordHash.verify({
390
+ plainValue: "anything",
391
+ storedValue: "",
392
+ salt: SALT,
393
+ }),
394
+ ).resolves.toBe(false);
395
+ });
396
+ });
397
+
398
+ describe("PasswordHash.verify — the schemes that came before", () => {
399
+ test("a bare SHA-256 hash from before salts existed still verifies", async () => {
400
+ const legacy: string = await HashedString.hashValue(
401
+ "old-password",
402
+ EncryptionSecret,
403
+ );
404
+
405
+ await expect(
406
+ PasswordHash.verify({
407
+ plainValue: "old-password",
408
+ storedValue: legacy,
409
+ salt: null,
410
+ }),
411
+ ).resolves.toBe(true);
412
+
413
+ await expect(
414
+ PasswordHash.verify({
415
+ plainValue: "guess",
416
+ storedValue: legacy,
417
+ salt: null,
418
+ }),
419
+ ).resolves.toBe(false);
420
+ });
421
+
422
+ test("a salted SHA-256 hash still verifies", async () => {
423
+ const saltedSha: string = await HashedString.hashValue(
424
+ "interim-password",
425
+ EncryptionSecret,
426
+ SALT,
427
+ );
428
+
429
+ await expect(
430
+ PasswordHash.verify({
431
+ plainValue: "interim-password",
432
+ storedValue: saltedSha,
433
+ salt: SALT,
434
+ }),
435
+ ).resolves.toBe(true);
436
+
437
+ await expect(
438
+ PasswordHash.verify({
439
+ plainValue: "guess",
440
+ storedValue: saltedSha,
441
+ salt: SALT,
442
+ }),
443
+ ).resolves.toBe(false);
444
+ });
445
+
446
+ test("a salted SHA-256 hash checked without its salt fails, it does not throw", async () => {
447
+ /*
448
+ * Only scrypt hashes throw on a missing salt. A bare digest is
449
+ * indistinguishable between the two SHA-256 schemes, so the salt's
450
+ * absence is the scheme selector rather than an error.
451
+ */
452
+ const saltedSha: string = await HashedString.hashValue(
453
+ "interim-password",
454
+ EncryptionSecret,
455
+ SALT,
456
+ );
457
+
458
+ await expect(
459
+ PasswordHash.verify({
460
+ plainValue: "interim-password",
461
+ storedValue: saltedSha,
462
+ salt: null,
463
+ }),
464
+ ).resolves.toBe(false);
465
+ });
466
+ });
467
+
468
+ describe("PasswordHash.verify — malformed stored values", () => {
469
+ type CaseList = ReadonlyArray<string>;
470
+
471
+ const MALFORMED: CaseList = [
472
+ "scrypt$",
473
+ "scrypt$$",
474
+ "scrypt$N=16384,r=8,p=1$",
475
+ "scrypt$N=16384,r=8$deadbeef",
476
+ "scrypt$garbage$deadbeef",
477
+ "scrypt$N=16384,r=8,p=1$deadbeef$extra",
478
+ "bcrypt$N=16384,r=8,p=1$deadbeef",
479
+ "$scrypt$N=16384,r=8,p=1$deadbeef",
480
+ "scrypt$N=0,r=8,p=1$deadbeef",
481
+ "scrypt$N=16383,r=8,p=1$deadbeef", // not a power of two
482
+ "scrypt$N=99999999999,r=8,p=1$deadbeef", // absurd memory ask
483
+ "scrypt$N=16384,r=0,p=1$deadbeef",
484
+ "scrypt$N=16384,r=8,p=0$deadbeef",
485
+ "scrypt$N=16384,r=999,p=1$deadbeef",
486
+ ];
487
+
488
+ test.each(MALFORMED)(
489
+ "%s is rejected without throwing and without allocating",
490
+ async (storedValue: string) => {
491
+ /*
492
+ * Anything unparseable falls through to the legacy SHA-256 comparison,
493
+ * where it simply will not match. It must never throw, and an absurd N
494
+ * must never reach scrypt.
495
+ */
496
+ await expect(
497
+ PasswordHash.verify({
498
+ plainValue: "password",
499
+ storedValue: storedValue,
500
+ salt: SALT,
501
+ }),
502
+ ).resolves.toBe(false);
503
+ },
504
+ );
505
+ });
506
+
507
+ describe("PasswordHash.needsUpgrade", () => {
508
+ test("a hash written today does not need upgrading", async () => {
509
+ const stored: string = await PasswordHash.hash({
510
+ plainValue: "password",
511
+ salt: SALT,
512
+ });
513
+
514
+ expect(PasswordHash.needsUpgrade(stored)).toBe(false);
515
+ });
516
+
517
+ test("a bare SHA-256 hash needs upgrading", async () => {
518
+ expect(
519
+ PasswordHash.needsUpgrade(
520
+ await HashedString.hashValue("password", EncryptionSecret),
521
+ ),
522
+ ).toBe(true);
523
+ });
524
+
525
+ test("a salted SHA-256 hash needs upgrading", async () => {
526
+ expect(
527
+ PasswordHash.needsUpgrade(
528
+ await HashedString.hashValue("password", EncryptionSecret, SALT),
529
+ ),
530
+ ).toBe(true);
531
+ });
532
+
533
+ test("a scrypt hash at stale cost parameters needs upgrading", () => {
534
+ /*
535
+ * This is what turns a cost bump into a migration: change the parameters
536
+ * and every existing hash starts reporting true here.
537
+ */
538
+ expect(
539
+ PasswordHash.needsUpgrade(`scrypt$N=1024,r=8,p=1$${"a".repeat(64)}`),
540
+ ).toBe(true);
541
+ });
542
+
543
+ test("an empty stored value does not, because there is nothing to upgrade", () => {
544
+ expect(PasswordHash.needsUpgrade("")).toBe(false);
545
+ });
546
+
547
+ test("the current prefix is exactly what hash() writes", async () => {
548
+ const stored: string = await PasswordHash.hash({
549
+ plainValue: "password",
550
+ salt: SALT,
551
+ });
552
+
553
+ expect(stored.startsWith(PasswordHash.getCurrentPrefix())).toBe(true);
554
+ });
555
+ });