@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,539 @@
1
+ import GitHubAPI from "../../../Server/API/GitHubAPI";
2
+ import GitHubUtil from "../../../Server/Utils/CodeRepository/GitHub/GitHub";
3
+ import CodeRepositoryService from "../../../Server/Services/CodeRepositoryService";
4
+ import ProjectService from "../../../Server/Services/ProjectService";
5
+ import AccessTokenService from "../../../Server/Services/AccessTokenService";
6
+ import JSONWebToken from "../../../Server/Utils/JsonWebToken";
7
+ import Response from "../../../Server/Utils/Response";
8
+ import {
9
+ ExpressRequest,
10
+ ExpressResponse,
11
+ NextFunction,
12
+ } from "../../../Server/Utils/Express";
13
+ import { mockRouter } from "./Helpers";
14
+ import Dictionary from "../../../Types/Dictionary";
15
+ import BadDataException from "../../../Types/Exception/BadDataException";
16
+ import NotAuthenticatedException from "../../../Types/Exception/NotAuthenticatedException";
17
+ import NotAuthorizedException from "../../../Types/Exception/NotAuthorizedException";
18
+ import ObjectID from "../../../Types/ObjectID";
19
+ import Permission, {
20
+ UserPermission,
21
+ UserTenantAccessPermission,
22
+ } from "../../../Types/Permission";
23
+ import { JSONObject } from "../../../Types/JSON";
24
+ import {
25
+ afterEach,
26
+ beforeAll,
27
+ beforeEach,
28
+ describe,
29
+ expect,
30
+ test,
31
+ } from "@jest/globals";
32
+
33
+ /*
34
+ * GHSA-xx95-gmcf-7q86 — cross-tenant GitHub repository token minting.
35
+ *
36
+ * Project.gitHubAppInstallationId is what every token-minting path trusts to
37
+ * decide which tenant owns an installation. This file pins the only route
38
+ * allowed to write it.
39
+ *
40
+ * The original hole was that "who owns this installation?" was answered by
41
+ * whoever asked. `/github/auth/install` signed a state for any (project, user)
42
+ * pair named in the query string, without even requiring a session; the
43
+ * callback then took `installation_id` straight off the URL. So an attacker
44
+ * could start an install for a project they own, edit the callback URL to a
45
+ * victim's installation ID, and have OneUptime record their project as the
46
+ * owner — after which the victim's private repositories were imported into it
47
+ * and write-scoped `ghs_` tokens were minted on request.
48
+ */
49
+
50
+ // The install route refuses to run at all unless a GitHub App is configured.
51
+ jest.mock("../../../Server/EnvironmentConfig", () => {
52
+ return {
53
+ ...(jest.requireActual("../../../Server/EnvironmentConfig") as Record<
54
+ string,
55
+ unknown
56
+ >),
57
+ GitHubAppName: "oneuptime-test-app",
58
+ };
59
+ });
60
+
61
+ jest.mock("../../../Server/Utils/Express", () => {
62
+ return {
63
+ getRouter: () => {
64
+ return mockRouter;
65
+ },
66
+ };
67
+ });
68
+
69
+ jest.mock("../../../Server/Utils/Response", () => {
70
+ return {
71
+ sendJsonObjectResponse: jest.fn().mockImplementation((...args: []) => {
72
+ return args;
73
+ }),
74
+ sendEmptySuccessResponse: jest.fn(),
75
+ sendEntityResponse: jest.fn().mockImplementation((...args: []) => {
76
+ return args;
77
+ }),
78
+ sendErrorResponse: jest.fn().mockImplementation((...args: []) => {
79
+ return args;
80
+ }),
81
+ redirect: jest.fn().mockImplementation((...args: []) => {
82
+ return args;
83
+ }),
84
+ };
85
+ });
86
+
87
+ const INSTALL_ROUTE: string = "/github/auth/install";
88
+ const CALLBACK_ROUTE: string = "/github/auth/callback";
89
+
90
+ // The two routes the fix removed.
91
+ const REMOVED_LIST_REPOSITORIES_ROUTE: string =
92
+ "/github/repositories/:projectId/:installationId";
93
+ const REMOVED_CONNECT_ROUTE: string = "/github/repository/connect";
94
+
95
+ function thrownError(): Error | undefined {
96
+ const sendErrorResponse: jest.Mock =
97
+ Response.sendErrorResponse as unknown as jest.Mock;
98
+
99
+ if (sendErrorResponse.mock.calls.length === 0) {
100
+ return undefined;
101
+ }
102
+
103
+ return sendErrorResponse.mock.calls[0]![2] as Error;
104
+ }
105
+
106
+ async function callRoute(data: {
107
+ method: string;
108
+ uri: string;
109
+ query?: Dictionary<string> | undefined;
110
+ userId?: ObjectID | undefined;
111
+ }): Promise<void> {
112
+ const req: ExpressRequest = {
113
+ params: {},
114
+ query: data.query || {},
115
+ body: {},
116
+ headers: {},
117
+ userAuthorization: data.userId
118
+ ? {
119
+ userId: data.userId,
120
+ }
121
+ : undefined,
122
+ } as unknown as ExpressRequest;
123
+
124
+ const res: ExpressResponse = {
125
+ send: jest.fn(),
126
+ json: jest.fn(),
127
+ status: jest.fn().mockReturnThis(),
128
+ redirect: jest.fn(),
129
+ } as unknown as ExpressResponse;
130
+
131
+ const next: jest.Mock = jest.fn();
132
+
133
+ await mockRouter
134
+ .match(data.method, data.uri)
135
+ .handlerFunction(req, res, next as unknown as NextFunction);
136
+ }
137
+
138
+ describe("GitHub App installation binding", () => {
139
+ let projectId: ObjectID;
140
+ let userId: ObjectID;
141
+ let victimInstallationId: string;
142
+
143
+ beforeAll(() => {
144
+ mockRouter.routes.length = 0;
145
+ new GitHubAPI().getRouter();
146
+ });
147
+
148
+ beforeEach(() => {
149
+ jest.clearAllMocks();
150
+ projectId = ObjectID.generate();
151
+ userId = ObjectID.generate();
152
+ victimInstallationId = "99999999";
153
+ });
154
+
155
+ afterEach(() => {
156
+ jest.restoreAllMocks();
157
+ });
158
+
159
+ function grantProjectAccess(): void {
160
+ const permission: UserPermission = {
161
+ _type: "UserPermission",
162
+ permission: Permission.ProjectOwner,
163
+ labelIds: [],
164
+ };
165
+
166
+ const tenantPermission: UserTenantAccessPermission = {
167
+ _type: "UserTenantAccessPermission",
168
+ projectId: projectId,
169
+ permissions: [permission],
170
+ };
171
+
172
+ jest
173
+ .spyOn(AccessTokenService, "getUserTenantAccessPermission")
174
+ .mockResolvedValue(tenantPermission);
175
+ }
176
+
177
+ function denyProjectAccess(): void {
178
+ jest
179
+ .spyOn(AccessTokenService, "getUserTenantAccessPermission")
180
+ .mockResolvedValue(null);
181
+ }
182
+
183
+ /*
184
+ * The routes that made the installation ID a client-supplied input. Neither
185
+ * had a caller — the dashboard only ever uses install -> callback — so they
186
+ * are gone rather than patched. If one is ever re-added, it has to justify
187
+ * itself against this advisory first.
188
+ */
189
+ describe("removed routes", () => {
190
+ test.each([
191
+ ["GET", REMOVED_LIST_REPOSITORIES_ROUTE],
192
+ ["POST", REMOVED_CONNECT_ROUTE],
193
+ ])("%s %s is not registered", (method: string, uri: string) => {
194
+ const exists: boolean = mockRouter.routes.some(
195
+ (route: { method: string; uri: string }) => {
196
+ return route.method === method && route.uri === uri;
197
+ },
198
+ );
199
+
200
+ expect(exists).toBe(false);
201
+ });
202
+
203
+ test("no remaining route accepts an installation id as a request parameter", () => {
204
+ const routesTakingInstallationId: Array<string> = mockRouter.routes
205
+ .filter((route: { uri: string }) => {
206
+ return route.uri.includes(":installationId");
207
+ })
208
+ .map((route: { uri: string }) => {
209
+ return route.uri;
210
+ });
211
+
212
+ expect(routesTakingInstallationId).toEqual([]);
213
+ });
214
+ });
215
+
216
+ describe("GET /github/auth/install", () => {
217
+ test("rejects an unauthenticated caller", async () => {
218
+ await callRoute({ method: "GET", uri: INSTALL_ROUTE, query: {} });
219
+
220
+ expect(thrownError()).toBeInstanceOf(NotAuthenticatedException);
221
+ });
222
+
223
+ test("never signs a state for an unauthenticated caller", async () => {
224
+ const signSpy: jest.SpyInstance = jest.spyOn(
225
+ JSONWebToken,
226
+ "signJsonPayload",
227
+ );
228
+
229
+ await callRoute({
230
+ method: "GET",
231
+ uri: INSTALL_ROUTE,
232
+ query: { projectId: projectId.toString(), userId: userId.toString() },
233
+ });
234
+
235
+ expect(thrownError()).toBeInstanceOf(NotAuthenticatedException);
236
+ expect(signSpy).not.toHaveBeenCalled();
237
+ });
238
+
239
+ test("rejects a caller who is not a member of the project", async () => {
240
+ denyProjectAccess();
241
+
242
+ await callRoute({
243
+ method: "GET",
244
+ uri: INSTALL_ROUTE,
245
+ query: { projectId: projectId.toString() },
246
+ userId: userId,
247
+ });
248
+
249
+ expect(thrownError()).toBeInstanceOf(NotAuthorizedException);
250
+ });
251
+
252
+ test("requires a project id", async () => {
253
+ await callRoute({
254
+ method: "GET",
255
+ uri: INSTALL_ROUTE,
256
+ query: {},
257
+ userId: userId,
258
+ });
259
+
260
+ expect(thrownError()).toBeInstanceOf(BadDataException);
261
+ });
262
+
263
+ /*
264
+ * The state binds the install to a user. Taking that user from the query
265
+ * string let anyone mint a state naming somebody else; it must come from
266
+ * the session.
267
+ */
268
+ test("ignores a userId in the query string and uses the session user", async () => {
269
+ grantProjectAccess();
270
+
271
+ const spoofedUserId: ObjectID = ObjectID.generate();
272
+ const signSpy: jest.SpyInstance = jest
273
+ .spyOn(JSONWebToken, "signJsonPayload")
274
+ .mockReturnValue("signed-state");
275
+
276
+ await callRoute({
277
+ method: "GET",
278
+ uri: INSTALL_ROUTE,
279
+ query: {
280
+ projectId: projectId.toString(),
281
+ userId: spoofedUserId.toString(),
282
+ },
283
+ userId: userId,
284
+ });
285
+
286
+ expect(signSpy).toHaveBeenCalledTimes(1);
287
+
288
+ const payload: JSONObject = signSpy.mock.calls[0]![0] as JSONObject;
289
+ expect(payload["userId"]).toBe(userId.toString());
290
+ expect(payload["userId"]).not.toBe(spoofedUserId.toString());
291
+ expect(payload["projectId"]).toBe(projectId.toString());
292
+ });
293
+
294
+ test("checks project access for the session user, not the query user", async () => {
295
+ grantProjectAccess();
296
+ jest.spyOn(JSONWebToken, "signJsonPayload").mockReturnValue("state");
297
+
298
+ const accessSpy: jest.SpyInstance = jest.spyOn(
299
+ AccessTokenService,
300
+ "getUserTenantAccessPermission",
301
+ );
302
+
303
+ await callRoute({
304
+ method: "GET",
305
+ uri: INSTALL_ROUTE,
306
+ query: {
307
+ projectId: projectId.toString(),
308
+ userId: ObjectID.generate().toString(),
309
+ },
310
+ userId: userId,
311
+ });
312
+
313
+ expect(accessSpy).toHaveBeenCalledTimes(1);
314
+ expect((accessSpy.mock.calls[0]![0] as ObjectID).toString()).toBe(
315
+ userId.toString(),
316
+ );
317
+ expect((accessSpy.mock.calls[0]![1] as ObjectID).toString()).toBe(
318
+ projectId.toString(),
319
+ );
320
+ });
321
+
322
+ test("redirects a legitimate member to GitHub", async () => {
323
+ grantProjectAccess();
324
+ jest.spyOn(JSONWebToken, "signJsonPayload").mockReturnValue("state");
325
+
326
+ await callRoute({
327
+ method: "GET",
328
+ uri: INSTALL_ROUTE,
329
+ query: { projectId: projectId.toString() },
330
+ userId: userId,
331
+ });
332
+
333
+ expect(thrownError()).toBeUndefined();
334
+ expect(Response.redirect).toHaveBeenCalledTimes(1);
335
+ });
336
+ });
337
+
338
+ describe("GET /github/auth/callback", () => {
339
+ let updateProjectSpy: jest.SpyInstance;
340
+ let importSpy: jest.SpyInstance;
341
+ let verifySpy: jest.SpyInstance;
342
+
343
+ beforeEach(() => {
344
+ jest.spyOn(JSONWebToken, "decodeJsonPayload").mockReturnValue({
345
+ projectId: projectId.toString(),
346
+ userId: userId.toString(),
347
+ });
348
+
349
+ grantProjectAccess();
350
+
351
+ updateProjectSpy = jest.spyOn(ProjectService, "updateOneById");
352
+ updateProjectSpy.mockResolvedValue(1);
353
+
354
+ importSpy = jest.spyOn(
355
+ CodeRepositoryService,
356
+ "importReposFromInstallation",
357
+ );
358
+ importSpy.mockResolvedValue({ imported: 0, skipped: 0 });
359
+
360
+ verifySpy = jest.spyOn(GitHubUtil, "assertUserControlsInstallation");
361
+ verifySpy.mockResolvedValue(undefined);
362
+ });
363
+
364
+ function callbackQuery(overrides?: Dictionary<string>): Dictionary<string> {
365
+ return {
366
+ state: "signed-state",
367
+ installation_id: victimInstallationId,
368
+ code: "oauth-code",
369
+ ...(overrides || {}),
370
+ };
371
+ }
372
+
373
+ test("binds the installation to the project once GitHub confirms the installer controls it", async () => {
374
+ await callRoute({
375
+ method: "GET",
376
+ uri: CALLBACK_ROUTE,
377
+ query: callbackQuery(),
378
+ });
379
+
380
+ expect(thrownError()).toBeUndefined();
381
+ expect(updateProjectSpy).toHaveBeenCalledTimes(1);
382
+
383
+ const updateArgs: {
384
+ id: ObjectID;
385
+ data: { gitHubAppInstallationId?: string };
386
+ } = updateProjectSpy.mock.calls[0]![0] as {
387
+ id: ObjectID;
388
+ data: { gitHubAppInstallationId?: string };
389
+ };
390
+ expect(updateArgs.id.toString()).toBe(projectId.toString());
391
+ expect(updateArgs.data.gitHubAppInstallationId).toBe(
392
+ victimInstallationId,
393
+ );
394
+ });
395
+
396
+ test("verifies the exact installation id from the URL, not some other one", async () => {
397
+ await callRoute({
398
+ method: "GET",
399
+ uri: CALLBACK_ROUTE,
400
+ query: callbackQuery(),
401
+ });
402
+
403
+ expect(verifySpy).toHaveBeenCalledTimes(1);
404
+
405
+ const verifyArgs: { oauthCode: string; installationId: string } =
406
+ verifySpy.mock.calls[0]![0] as {
407
+ oauthCode: string;
408
+ installationId: string;
409
+ };
410
+ expect(verifyArgs.installationId).toBe(victimInstallationId);
411
+ expect(verifyArgs.oauthCode).toBe("oauth-code");
412
+ });
413
+
414
+ /*
415
+ * The core of the fix. A valid state only proves "this user asked to
416
+ * install something for this project" — it says nothing about WHICH
417
+ * installation. Without the OAuth round trip, hand-editing installation_id
418
+ * is enough to claim someone else's installation.
419
+ */
420
+ test("refuses to bind an installation the installer does not control", async () => {
421
+ verifySpy.mockRejectedValue(
422
+ new BadDataException("does not have access to this installation"),
423
+ );
424
+
425
+ await callRoute({
426
+ method: "GET",
427
+ uri: CALLBACK_ROUTE,
428
+ query: callbackQuery(),
429
+ });
430
+
431
+ expect(thrownError()).toBeInstanceOf(BadDataException);
432
+ expect(updateProjectSpy).not.toHaveBeenCalled();
433
+ expect(importSpy).not.toHaveBeenCalled();
434
+ });
435
+
436
+ test("refuses to bind when GitHub returned no OAuth code to verify with", async () => {
437
+ const query: Dictionary<string> = callbackQuery();
438
+ delete query["code"];
439
+
440
+ await callRoute({
441
+ method: "GET",
442
+ uri: CALLBACK_ROUTE,
443
+ query: query,
444
+ });
445
+
446
+ expect(thrownError()).toBeInstanceOf(BadDataException);
447
+ expect(verifySpy).not.toHaveBeenCalled();
448
+ expect(updateProjectSpy).not.toHaveBeenCalled();
449
+ expect(importSpy).not.toHaveBeenCalled();
450
+ });
451
+
452
+ /*
453
+ * Import is what pulls the victim's private repositories into the
454
+ * attacker's project, so it must sit strictly behind verification.
455
+ */
456
+ test("does not import any repositories when verification fails", async () => {
457
+ verifySpy.mockRejectedValue(new BadDataException("nope"));
458
+
459
+ await callRoute({
460
+ method: "GET",
461
+ uri: CALLBACK_ROUTE,
462
+ query: callbackQuery(),
463
+ });
464
+
465
+ expect(importSpy).not.toHaveBeenCalled();
466
+ });
467
+
468
+ test("verifies before writing the binding", async () => {
469
+ const callOrder: Array<string> = [];
470
+
471
+ verifySpy.mockImplementation(async (): Promise<void> => {
472
+ callOrder.push("verify");
473
+ });
474
+ updateProjectSpy.mockImplementation(async (): Promise<void> => {
475
+ callOrder.push("bind");
476
+ });
477
+
478
+ await callRoute({
479
+ method: "GET",
480
+ uri: CALLBACK_ROUTE,
481
+ query: callbackQuery(),
482
+ });
483
+
484
+ expect(callOrder).toEqual(["verify", "bind"]);
485
+ });
486
+
487
+ test("rejects a caller whose state names a project they cannot access", async () => {
488
+ denyProjectAccess();
489
+
490
+ await callRoute({
491
+ method: "GET",
492
+ uri: CALLBACK_ROUTE,
493
+ query: callbackQuery(),
494
+ });
495
+
496
+ expect(thrownError()).toBeInstanceOf(NotAuthorizedException);
497
+ expect(verifySpy).not.toHaveBeenCalled();
498
+ expect(updateProjectSpy).not.toHaveBeenCalled();
499
+ });
500
+
501
+ test("rejects a missing state", async () => {
502
+ const query: Dictionary<string> = callbackQuery();
503
+ delete query["state"];
504
+
505
+ await callRoute({ method: "GET", uri: CALLBACK_ROUTE, query: query });
506
+
507
+ expect(thrownError()).toBeInstanceOf(BadDataException);
508
+ expect(updateProjectSpy).not.toHaveBeenCalled();
509
+ });
510
+
511
+ test("rejects a state that is not signed by this instance", async () => {
512
+ jest
513
+ .spyOn(JSONWebToken, "decodeJsonPayload")
514
+ .mockImplementation((): never => {
515
+ throw new Error("invalid signature");
516
+ });
517
+
518
+ await callRoute({
519
+ method: "GET",
520
+ uri: CALLBACK_ROUTE,
521
+ query: callbackQuery(),
522
+ });
523
+
524
+ expect(thrownError()).toBeInstanceOf(BadDataException);
525
+ expect(verifySpy).not.toHaveBeenCalled();
526
+ expect(updateProjectSpy).not.toHaveBeenCalled();
527
+ });
528
+
529
+ test("rejects a missing installation id", async () => {
530
+ const query: Dictionary<string> = callbackQuery();
531
+ delete query["installation_id"];
532
+
533
+ await callRoute({ method: "GET", uri: CALLBACK_ROUTE, query: query });
534
+
535
+ expect(thrownError()).toBeInstanceOf(BadDataException);
536
+ expect(updateProjectSpy).not.toHaveBeenCalled();
537
+ });
538
+ });
539
+ });
@@ -31,11 +31,16 @@ const mockRouterForMethod: MockRouterForMethodFunction = (method: string) => {
31
31
  middleware: RouterFunction,
32
32
  handlerFunction: RouterFunction,
33
33
  ): void => {
34
+ /*
35
+ * Routes registered without middleware — router.get(uri, handler) — hand
36
+ * us the handler in the middleware slot. Normalise so `handlerFunction` is
37
+ * always the thing that actually serves the request.
38
+ */
34
39
  mockRouter.routes.push({
35
40
  method: method.toUpperCase(),
36
41
  uri,
37
42
  middleware,
38
- handlerFunction,
43
+ handlerFunction: handlerFunction || middleware,
39
44
  });
40
45
  };
41
46
  };