@oneuptime/common 12.0.15 → 12.0.16

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 (707) hide show
  1. package/Models/AnalyticsModels/Index.ts +10 -0
  2. package/Models/AnalyticsModels/LogCountBaseline.ts +174 -0
  3. package/Models/AnalyticsModels/SecurityEvent.ts +794 -0
  4. package/Models/AnalyticsModels/Span.ts +29 -1
  5. package/Models/AnalyticsModels/SpanCountBaseline.ts +198 -0
  6. package/Models/DatabaseModels/Alert.ts +42 -0
  7. package/Models/DatabaseModels/AlertMeasurement.ts +1114 -0
  8. package/Models/DatabaseModels/AlertMeasurementValue.ts +501 -0
  9. package/Models/DatabaseModels/AlertStateTimeline.ts +10 -2
  10. package/Models/DatabaseModels/BillingPaymentMethod.ts +8 -10
  11. package/Models/DatabaseModels/DetectionRule.ts +487 -0
  12. package/Models/DatabaseModels/GoogleSecOpsConnection.ts +382 -0
  13. package/Models/DatabaseModels/Incident.ts +42 -0
  14. package/Models/DatabaseModels/IncidentMeasurement.ts +1114 -0
  15. package/Models/DatabaseModels/IncidentMeasurementValue.ts +501 -0
  16. package/Models/DatabaseModels/IncidentStateTimeline.ts +10 -2
  17. package/Models/DatabaseModels/IncomingCallPolicyLabelRule.ts +36 -0
  18. package/Models/DatabaseModels/IncomingCallPolicyOwnerRule.ts +42 -0
  19. package/Models/DatabaseModels/Index.ts +20 -0
  20. package/Models/DatabaseModels/Label.ts +11 -0
  21. package/Models/DatabaseModels/LlmCostBudget.ts +655 -0
  22. package/Models/DatabaseModels/LlmModelPrice.ts +481 -0
  23. package/Models/DatabaseModels/LogSavedView.ts +87 -0
  24. package/Models/DatabaseModels/MarketingConversion.ts +210 -8
  25. package/Models/DatabaseModels/MetricSavedView.ts +59 -0
  26. package/Models/DatabaseModels/OnCallDutyPolicyLabelRule.ts +36 -0
  27. package/Models/DatabaseModels/OnCallDutyPolicyOwnerRule.ts +42 -0
  28. package/Models/DatabaseModels/OnCallDutyPolicyScheduleLabelRule.ts +36 -0
  29. package/Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerRule.ts +42 -0
  30. package/Models/DatabaseModels/Probe.ts +21 -0
  31. package/Models/DatabaseModels/RunbookLabelRule.ts +36 -0
  32. package/Models/DatabaseModels/RunbookOwnerRule.ts +42 -0
  33. package/Models/DatabaseModels/ScheduledMaintenanceMeasurement.ts +1104 -0
  34. package/Models/DatabaseModels/ScheduledMaintenanceMeasurementValue.ts +491 -0
  35. package/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.ts +10 -2
  36. package/Models/DatabaseModels/StatusPageLabelRule.ts +36 -0
  37. package/Models/DatabaseModels/StatusPageOwnerRule.ts +42 -0
  38. package/Models/DatabaseModels/TableView.ts +15 -0
  39. package/Models/DatabaseModels/Team.ts +13 -0
  40. package/Models/DatabaseModels/TeamMember.ts +8 -0
  41. package/Models/DatabaseModels/TraceSavedView.ts +52 -0
  42. package/Models/DatabaseModels/WorkflowLabelRule.ts +36 -0
  43. package/Models/DatabaseModels/WorkflowOwnerRule.ts +42 -0
  44. package/Server/API/TelemetryAPI.ts +252 -6
  45. package/Server/API/UserAPI.ts +88 -0
  46. package/Server/EnvironmentConfig.ts +36 -1
  47. package/Server/Infrastructure/Postgres/SchemaMigrations/1788000000000-AddDetectionRuleAndGoogleSecOpsConnection.ts +88 -0
  48. package/Server/Infrastructure/Postgres/SchemaMigrations/1788100000000-AddMeasurements.ts +457 -0
  49. package/Server/Infrastructure/Postgres/SchemaMigrations/1788200000000-AddLlmCostBudget.ts +97 -0
  50. package/Server/Infrastructure/Postgres/SchemaMigrations/1788300000000-AddLlmModelPrice.ts +45 -0
  51. package/Server/Infrastructure/Postgres/SchemaMigrations/1788400000000-AddMarketingConversionAttribution.ts +105 -0
  52. package/Server/Infrastructure/Postgres/SchemaMigrations/1788500000000-RemoveLlmCostBudgetAlertColumns.ts +77 -0
  53. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +12 -0
  54. package/Server/Middleware/IdentityRateLimit.ts +672 -0
  55. package/Server/Services/AccessTokenService.ts +15 -2
  56. package/Server/Services/AlertMeasurementService.ts +302 -0
  57. package/Server/Services/AlertMeasurementValueService.ts +508 -0
  58. package/Server/Services/AlertService.ts +50 -0
  59. package/Server/Services/AlertStateTimelineService.ts +54 -0
  60. package/Server/Services/DetectionRuleService.ts +74 -0
  61. package/Server/Services/GoogleSecOpsConnectionService.ts +89 -0
  62. package/Server/Services/IncidentMeasurementService.ts +304 -0
  63. package/Server/Services/IncidentMeasurementValueService.ts +523 -0
  64. package/Server/Services/IncidentService.ts +51 -0
  65. package/Server/Services/IncidentStateTimelineService.ts +54 -0
  66. package/Server/Services/Index.ts +30 -0
  67. package/Server/Services/LlmCostBudgetService.ts +93 -0
  68. package/Server/Services/LlmModelPriceService.ts +202 -0
  69. package/Server/Services/LogAggregationService.ts +1106 -2
  70. package/Server/Services/LogCountBaselineService.ts +163 -0
  71. package/Server/Services/MutableMetricService.ts +30 -9
  72. package/Server/Services/ScheduledMaintenanceMeasurementService.ts +311 -0
  73. package/Server/Services/ScheduledMaintenanceMeasurementValueService.ts +546 -0
  74. package/Server/Services/ScheduledMaintenanceService.ts +53 -0
  75. package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +188 -1
  76. package/Server/Services/SecurityEventService.ts +104 -0
  77. package/Server/Services/SpanCountBaselineService.ts +173 -0
  78. package/Server/Services/StatusPageService.ts +4 -3
  79. package/Server/Services/TelemetryUsageBillingService.ts +17 -2
  80. package/Server/Services/UserService.ts +284 -51
  81. package/Server/Services/UserTotpAuthService.ts +27 -86
  82. package/Server/Services/UserWebAuthnService.ts +26 -86
  83. package/Server/Types/Billing/MeteredPlan/AllMeteredPlans.ts +14 -0
  84. package/Server/Types/Database/Permissions/TenantPermission.ts +10 -0
  85. package/Server/Types/Database/QueryHelper.ts +1 -1
  86. package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +1 -1
  87. package/Server/Utils/AI/Toolbox/Index.ts +6 -0
  88. package/Server/Utils/AI/Toolbox/SecurityEventTools.ts +349 -0
  89. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +13 -4
  90. package/Server/Utils/Attribution.ts +130 -34
  91. package/Server/Utils/Marketing/ConversionUploadProvider.ts +98 -16
  92. package/Server/Utils/Marketing/Providers/GoogleAds.ts +70 -7
  93. package/Server/Utils/Marketing/Providers/LinkedIn.ts +54 -16
  94. package/Server/Utils/Marketing/Providers/Meta.ts +52 -18
  95. package/Server/Utils/Marketing/Providers/MicrosoftAds.ts +37 -10
  96. package/Server/Utils/Marketing/Providers/Reddit.ts +45 -15
  97. package/Server/Utils/Measurement/MeasurementDefinitionValidator.ts +209 -0
  98. package/Server/Utils/Measurement/MeasurementMetricWriter.ts +189 -0
  99. package/Server/Utils/Monitor/Criteria/CountAnomaly.ts +182 -0
  100. package/Server/Utils/Monitor/Criteria/LogMonitorCriteria.ts +132 -0
  101. package/Server/Utils/Monitor/Criteria/SecurityEventsMonitorCriteria.ts +35 -0
  102. package/Server/Utils/Monitor/Criteria/TraceMonitorCriteria.ts +132 -0
  103. package/Server/Utils/Monitor/DataToProcess.ts +2 -0
  104. package/Server/Utils/Monitor/MonitorCriteriaDataExtractor.ts +14 -0
  105. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +15 -0
  106. package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +20 -0
  107. package/Server/Utils/Monitor/MonitorResource.ts +16 -0
  108. package/Server/Utils/Monitor/MonitorStepResourceIdentity.ts +1 -0
  109. package/Server/Utils/Monitor/MonitorStepsReferenceExtractor.ts +1 -0
  110. package/Server/Utils/SecurityEvent/DetectionRuleEvaluator.ts +548 -0
  111. package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.ts +262 -0
  112. package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.ts +212 -0
  113. package/Server/Utils/SecurityEvent/SecurityEventRow.ts +101 -0
  114. package/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.ts +635 -0
  115. package/Server/Utils/Telemetry/LlmCostBudgetEvaluator.ts +379 -0
  116. package/Server/Utils/Telemetry/LlmSpan.ts +83 -4
  117. package/Server/Utils/Telemetry/LogErrorPatternSql.ts +136 -0
  118. package/Server/Utils/UserPermission/UserPermission.ts +57 -1
  119. package/Tests/App/Dashboard/AlertsSideMenu.test.tsx +4 -0
  120. package/Tests/App/Dashboard/CreatePageEntryPointPermissions.test.tsx +306 -0
  121. package/Tests/App/Dashboard/DangerZone.test.tsx +11 -0
  122. package/Tests/App/Dashboard/IncidentsSideMenu.test.tsx +4 -0
  123. package/Tests/App/Dashboard/LogMonitorAttributeOperatorPreview.test.tsx +459 -0
  124. package/Tests/App/Dashboard/OverviewCustomFields.test.tsx +24 -0
  125. package/Tests/App/Dashboard/ScheduledMaintenanceSideMenu.test.tsx +6 -0
  126. package/Tests/Models/DatabaseModels/DomainRoleTierCoverage.test.ts +323 -0
  127. package/Tests/Models/DatabaseModels/ProjectSharedResources.test.ts +386 -0
  128. package/Tests/Server/API/LogErrorPatternAPI.test.ts +811 -0
  129. package/Tests/Server/API/UserAuthenticationAPI.test.ts +11 -0
  130. package/Tests/Server/API/UserProjectsAPI.test.ts +11 -2
  131. package/Tests/Server/API/UserTwoFactorAuthAdminAPI.test.ts +918 -0
  132. package/Tests/Server/Infrastructure/LocalCache.test.ts +149 -0
  133. package/Tests/Server/Services/AccessTokenProjectUserGrant.test.ts +249 -0
  134. package/Tests/Server/Services/AlertMeasurementService.test.ts +665 -0
  135. package/Tests/Server/Services/AlertMeasurementValueService.test.ts +389 -0
  136. package/Tests/Server/Services/DetectionRuleService.test.ts +254 -0
  137. package/Tests/Server/Services/IncidentMeasurementService.test.ts +394 -0
  138. package/Tests/Server/Services/IncidentMeasurementValueService.test.ts +816 -0
  139. package/Tests/Server/Services/LlmCostBudgetService.test.ts +104 -0
  140. package/Tests/Server/Services/LogAggregationAttributeOperators.test.ts +405 -0
  141. package/Tests/Server/Services/LogErrorPatternAggregation.test.ts +804 -0
  142. package/Tests/Server/Services/MutableMetricNameRouting.test.ts +83 -0
  143. package/Tests/Server/Services/ScheduledMaintenanceMeasurementService.test.ts +750 -0
  144. package/Tests/Server/Services/ScheduledMaintenanceMeasurementValueService.test.ts +478 -0
  145. package/Tests/Server/Services/UserAuthenticationService.test.ts +216 -4
  146. package/Tests/Server/Services/UserTwoFactorAuthAdmin.test.ts +1190 -0
  147. package/Tests/Server/Types/Database/Permissions/ScopedRoleSharedResourceAccess.test.ts +466 -0
  148. package/Tests/Server/Types/Database/QueryHelperOperators.test.ts +265 -0
  149. package/Tests/Server/Types/Database/QueryHelperTextAndStructuralOperators.test.ts +206 -0
  150. package/Tests/Server/Utils/AnalyticsDatabase/ClusterAwareSchema.test.ts +6 -0
  151. package/Tests/Server/Utils/AnalyticsDatabase/FractionalAttributeThreshold.test.ts +224 -0
  152. package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +172 -2
  153. package/Tests/Server/Utils/Attribution.test.ts +228 -1
  154. package/Tests/Server/Utils/Marketing/AdUploadableConversionTypes.test.ts +153 -92
  155. package/Tests/Server/Utils/Marketing/ConversionUploadProvider.test.ts +123 -6
  156. package/Tests/Server/Utils/Marketing/GoogleAds.test.ts +253 -1
  157. package/Tests/Server/Utils/Marketing/LinkedIn.test.ts +282 -0
  158. package/Tests/Server/Utils/Marketing/Meta.test.ts +304 -0
  159. package/Tests/Server/Utils/Marketing/MicrosoftAds.test.ts +263 -0
  160. package/Tests/Server/Utils/Marketing/Reddit.test.ts +259 -0
  161. package/Tests/Server/Utils/Measurement/MeasurementDefinitionValidator.test.ts +322 -0
  162. package/Tests/Server/Utils/Measurement/MeasurementMetricWriter.test.ts +373 -0
  163. package/Tests/Server/Utils/Monitor/Criteria/CountAnomaly.test.ts +221 -0
  164. package/Tests/Server/Utils/Monitor/Criteria/LogMonitorCriteria.test.ts +236 -0
  165. package/Tests/Server/Utils/Monitor/Criteria/SecurityEventsMonitorCriteria.test.ts +147 -0
  166. package/Tests/Server/Utils/Monitor/Criteria/TraceMonitorCriteria.test.ts +274 -0
  167. package/Tests/Server/Utils/Monitor/SecurityEventsMonitorDataExtractor.test.ts +93 -0
  168. package/Tests/Server/Utils/SecurityEvent/DetectionRuleEvaluator.test.ts +660 -0
  169. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsClient.test.ts +308 -0
  170. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPoller.test.ts +271 -0
  171. package/Tests/Server/Utils/SecurityEvent/SecurityEventRow.test.ts +272 -0
  172. package/Tests/Server/Utils/SecurityEvent/SigmaClickhouseCompiler.test.ts +511 -0
  173. package/Tests/Server/Utils/Telemetry/LlmCostBudgetEvaluator.test.ts +497 -0
  174. package/Tests/Server/Utils/Telemetry/LlmSpan.test.ts +380 -0
  175. package/Tests/Server/Utils/Telemetry/LogErrorPatternSql.test.ts +207 -0
  176. package/Tests/Server/Utils/UserPermission/ProjectUserGrant.test.ts +293 -0
  177. package/Tests/Types/Monitor/MonitorStepInfraMonitorUtils.test.ts +194 -0
  178. package/Tests/Types/Monitor/MonitorStepSecurityEventsMonitor.test.ts +201 -0
  179. package/Tests/Types/SecurityEvent/OcsfEventClass.test.ts +105 -0
  180. package/Tests/Types/SecurityEvent/OcsfSeverity.test.ts +111 -0
  181. package/Tests/Types/Telemetry/LlmCostCatalog.test.ts +516 -0
  182. package/Tests/UI/Components/ActiveFilterChipsOperatorValues.test.tsx +222 -0
  183. package/Tests/UI/Components/BulkActionPermissionGating.test.tsx +296 -0
  184. package/Tests/UI/Components/CardModelDetailPermissionGating.test.tsx +305 -0
  185. package/Tests/UI/Components/CustomFields/CustomFieldsDetail.test.tsx +23 -0
  186. package/Tests/UI/Components/DictionaryFilterOperatorDisplay.test.ts +239 -0
  187. package/Tests/UI/Components/DisabledButtonTooltip.test.tsx +267 -0
  188. package/Tests/UI/Components/DuplicateModel.test.tsx +37 -0
  189. package/Tests/UI/Components/ModelCardPermissionGating.test.tsx +348 -0
  190. package/Tests/UI/Components/ModelDelete.test.tsx +25 -1
  191. package/Tests/UI/Components/ModelFormCreatePermission.test.tsx +342 -0
  192. package/Tests/UI/Components/ModelTable/BaseModelTablePermissionGating.test.tsx +680 -0
  193. package/Tests/UI/Components/OrderedStatesListCreateGate.test.tsx +174 -0
  194. package/Tests/UI/Components/SavedViewsDropdownSearch.test.tsx +1127 -0
  195. package/Tests/UI/Components/SavedViewsFacetSection.test.tsx +958 -0
  196. package/Tests/UI/Components/SavedViewsList.test.ts +699 -0
  197. package/Tests/UI/Components/SavedViewsSidebarIntegration.test.tsx +640 -0
  198. package/Tests/UI/Components/SavedViewsSurfaceParity.test.tsx +679 -0
  199. package/Tests/UI/Components/TableViewLoadFailure.test.tsx +248 -0
  200. package/Tests/UI/Types/UseComponentOutsideClick.test.tsx +226 -0
  201. package/Tests/UI/Utils/PermissionGate.test.ts +467 -0
  202. package/Tests/Utils/Measurement/MeasurementEvaluator.test.ts +639 -0
  203. package/Tests/Utils/Monitor/MonitorSummarySnapshotUtil.test.ts +1 -0
  204. package/Tests/Utils/Monitor/NetworkDeviceLinkRuleUtil.test.ts +285 -0
  205. package/Tests/Utils/NetworkDevice/DeviceHealthStateUtil.test.ts +433 -0
  206. package/Tests/Utils/SecurityEvent/GenericNormalizer.test.ts +204 -0
  207. package/Tests/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.test.ts +252 -0
  208. package/Tests/Utils/SecurityEvent/NormalizerHelpers.test.ts +274 -0
  209. package/Tests/Utils/SecurityEvent/OcsfNormalizer.test.ts +208 -0
  210. package/Tests/Utils/SecurityEvent/SecurityEventNormalizer.test.ts +172 -0
  211. package/Tests/Utils/SecurityEvent/SigmaRuleParser.test.ts +471 -0
  212. package/Tests/Utils/SecurityEvent/UdmNormalizer.test.ts +361 -0
  213. package/Tests/Utils/Telemetry/CrossSignalScope.test.ts +82 -0
  214. package/Tests/Utils/Telemetry/LogErrorPattern.test.ts +463 -0
  215. package/Types/AI/AIChatTypes.ts +1 -0
  216. package/Types/Alerts/AlertMeasurementAnchorType.ts +14 -0
  217. package/Types/Alerts/AlertStateRole.ts +11 -0
  218. package/Types/AnalyticsDatabase/AnalyticsTableName.ts +16 -0
  219. package/Types/Dashboard/Chart/ChartType.ts +1 -0
  220. package/Types/Dashboard/DashboardComponentType.ts +2 -0
  221. package/Types/Dashboard/DashboardComponents/DashboardSecurityEventsFlowComponent.ts +18 -0
  222. package/Types/Dashboard/DashboardComponents/DashboardSecurityEventsListComponent.ts +16 -0
  223. package/Types/Incident/IncidentMeasurementAnchorType.ts +34 -0
  224. package/Types/Incident/IncidentStateRole.ts +14 -0
  225. package/Types/Marketing/Attribution.ts +75 -0
  226. package/Types/Marketing/MarketingConversion.ts +37 -7
  227. package/Types/Measurement/MeasurementAggregationType.ts +43 -0
  228. package/Types/Measurement/MeasurementAnchorKind.ts +23 -0
  229. package/Types/Measurement/MeasurementOccurrence.ts +14 -0
  230. package/Types/Measurement/MeasurementStatus.ts +34 -0
  231. package/Types/MeteredPlan/ProductType.ts +1 -0
  232. package/Types/Monitor/CriteriaFilter.ts +3 -0
  233. package/Types/Monitor/MonitorCriteriaInstance.ts +64 -0
  234. package/Types/Monitor/MonitorStep.ts +28 -0
  235. package/Types/Monitor/MonitorStepSecurityEventsMonitor.ts +100 -0
  236. package/Types/Monitor/MonitorType.ts +20 -0
  237. package/Types/Monitor/SecurityEventsMonitor/SecurityEventsMonitorResponse.ts +17 -0
  238. package/Types/Permission.ts +399 -2
  239. package/Types/ScheduledMaintenance/ScheduledMaintenanceMeasurementAnchorType.ts +20 -0
  240. package/Types/ScheduledMaintenance/ScheduledMaintenanceStateRole.ts +16 -0
  241. package/Types/SecurityEvent/NormalizedSecurityEvent.ts +92 -0
  242. package/Types/SecurityEvent/OcsfEventClass.ts +205 -0
  243. package/Types/SecurityEvent/OcsfSeverity.ts +88 -0
  244. package/Types/SecurityEvent/SecurityEventFormat.ts +23 -0
  245. package/Types/SecurityEvent/SigmaRule.ts +70 -0
  246. package/Types/Telemetry/LlmConventions.ts +20 -1
  247. package/Types/Telemetry/LlmCostCatalog.ts +557 -0
  248. package/Types/Telemetry/ServiceType.ts +1 -0
  249. package/Types/Telemetry/TelemetryRetentionConfig.ts +5 -1
  250. package/Types/Telemetry/TelemetryType.ts +1 -0
  251. package/Types/TwoFactorAuthStatus.ts +42 -0
  252. package/Types/UserAuthenticationStatus.ts +18 -0
  253. package/UI/Components/ActionButton/ActionButtonSchema.ts +7 -0
  254. package/UI/Components/BulkUpdate/BulkArchiveActions.tsx +35 -2
  255. package/UI/Components/BulkUpdate/BulkLabelActions.tsx +34 -1
  256. package/UI/Components/BulkUpdate/BulkOwnerActions.tsx +45 -1
  257. package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +3 -0
  258. package/UI/Components/Button/Button.tsx +37 -2
  259. package/UI/Components/Card/Card.tsx +6 -0
  260. package/UI/Components/CustomFields/CustomFieldsDetail.tsx +24 -1
  261. package/UI/Components/Dictionary/DictionaryFilterOperator.ts +57 -0
  262. package/UI/Components/DuplicateModel/DuplicateModel.tsx +16 -0
  263. package/UI/Components/Forms/ModelForm.tsx +54 -2
  264. package/UI/Components/List/ListRow.tsx +6 -0
  265. package/UI/Components/LogsViewer/components/ActiveFilterChips.tsx +34 -7
  266. package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +7 -40
  267. package/UI/Components/LogsViewer/components/SavedViewsDropdown.tsx +155 -3
  268. package/UI/Components/ModelDelete/ModelDelete.tsx +20 -0
  269. package/UI/Components/ModelDetail/CardModelDetail.tsx +36 -23
  270. package/UI/Components/ModelList/ModelList.tsx +21 -2
  271. package/UI/Components/ModelList/StaticModelList.tsx +11 -0
  272. package/UI/Components/ModelTable/BaseModelTable.tsx +168 -62
  273. package/UI/Components/ModelTable/ModelTable.tsx +16 -10
  274. package/UI/Components/ModelTable/TableView.tsx +36 -4
  275. package/UI/Components/MoreMenu/MoreMenuItem.tsx +36 -4
  276. package/UI/Components/OrderedStatesList/Item.tsx +6 -0
  277. package/UI/Components/OrderedStatesList/OrderedStatesList.tsx +81 -47
  278. package/UI/Components/ResetObjectID/ResetObjectID.tsx +16 -0
  279. package/UI/Components/SavedViews/SavedViewsFacetSection.tsx +242 -0
  280. package/UI/Components/SavedViews/SavedViewsList.ts +168 -0
  281. package/UI/Components/SavedViews/SavedViewsSearchInput.tsx +46 -0
  282. package/UI/Components/SavedViews/SavedViewsShowMoreButton.tsx +44 -0
  283. package/UI/Components/Table/TableRow.tsx +12 -0
  284. package/UI/Components/TelemetryViewer/components/SavedViewsDropdown.tsx +152 -2
  285. package/UI/Types/UseComponentOutsideClick.ts +47 -0
  286. package/UI/Utils/PermissionGate.ts +277 -0
  287. package/Utils/Dashboard/Components/DashboardSecurityEventsFlowComponent.ts +84 -0
  288. package/Utils/Dashboard/Components/DashboardSecurityEventsListComponent.ts +110 -0
  289. package/Utils/Dashboard/Components/Index.ts +14 -0
  290. package/Utils/Measurement/MeasurementEvaluator.ts +359 -0
  291. package/Utils/Monitor/MonitorSummarySnapshotUtil.ts +1 -0
  292. package/Utils/Monitor/NetworkDeviceLinkRuleUtil.ts +93 -11
  293. package/Utils/NetworkDevice/DeviceHealthStateUtil.ts +231 -0
  294. package/Utils/SecurityEvent/GenericNormalizer.ts +208 -0
  295. package/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.ts +206 -0
  296. package/Utils/SecurityEvent/NormalizerHelpers.ts +263 -0
  297. package/Utils/SecurityEvent/OcsfNormalizer.ts +194 -0
  298. package/Utils/SecurityEvent/SecurityEventNormalizer.ts +82 -0
  299. package/Utils/SecurityEvent/Sigma/SigmaRuleParser.ts +579 -0
  300. package/Utils/SecurityEvent/UdmNormalizer.ts +442 -0
  301. package/Utils/Telemetry/CrossSignalScope.ts +46 -9
  302. package/Utils/Telemetry/LogErrorPattern.ts +350 -0
  303. package/build/dist/Models/AnalyticsModels/Index.js +10 -0
  304. package/build/dist/Models/AnalyticsModels/Index.js.map +1 -1
  305. package/build/dist/Models/AnalyticsModels/LogCountBaseline.js +159 -0
  306. package/build/dist/Models/AnalyticsModels/LogCountBaseline.js.map +1 -0
  307. package/build/dist/Models/AnalyticsModels/SecurityEvent.js +596 -0
  308. package/build/dist/Models/AnalyticsModels/SecurityEvent.js.map +1 -0
  309. package/build/dist/Models/AnalyticsModels/Span.js +24 -1
  310. package/build/dist/Models/AnalyticsModels/Span.js.map +1 -1
  311. package/build/dist/Models/AnalyticsModels/SpanCountBaseline.js +181 -0
  312. package/build/dist/Models/AnalyticsModels/SpanCountBaseline.js.map +1 -0
  313. package/build/dist/Models/DatabaseModels/Alert.js +43 -0
  314. package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
  315. package/build/dist/Models/DatabaseModels/AlertMeasurement.js +1143 -0
  316. package/build/dist/Models/DatabaseModels/AlertMeasurement.js.map +1 -0
  317. package/build/dist/Models/DatabaseModels/AlertMeasurementValue.js +523 -0
  318. package/build/dist/Models/DatabaseModels/AlertMeasurementValue.js.map +1 -0
  319. package/build/dist/Models/DatabaseModels/AlertStateTimeline.js +9 -2
  320. package/build/dist/Models/DatabaseModels/AlertStateTimeline.js.map +1 -1
  321. package/build/dist/Models/DatabaseModels/BillingPaymentMethod.js +10 -11
  322. package/build/dist/Models/DatabaseModels/BillingPaymentMethod.js.map +1 -1
  323. package/build/dist/Models/DatabaseModels/DetectionRule.js +517 -0
  324. package/build/dist/Models/DatabaseModels/DetectionRule.js.map +1 -0
  325. package/build/dist/Models/DatabaseModels/GoogleSecOpsConnection.js +410 -0
  326. package/build/dist/Models/DatabaseModels/GoogleSecOpsConnection.js.map +1 -0
  327. package/build/dist/Models/DatabaseModels/Incident.js +43 -0
  328. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  329. package/build/dist/Models/DatabaseModels/IncidentMeasurement.js +1143 -0
  330. package/build/dist/Models/DatabaseModels/IncidentMeasurement.js.map +1 -0
  331. package/build/dist/Models/DatabaseModels/IncidentMeasurementValue.js +523 -0
  332. package/build/dist/Models/DatabaseModels/IncidentMeasurementValue.js.map +1 -0
  333. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js +9 -2
  334. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js.map +1 -1
  335. package/build/dist/Models/DatabaseModels/IncomingCallPolicyLabelRule.js +36 -0
  336. package/build/dist/Models/DatabaseModels/IncomingCallPolicyLabelRule.js.map +1 -1
  337. package/build/dist/Models/DatabaseModels/IncomingCallPolicyOwnerRule.js +42 -0
  338. package/build/dist/Models/DatabaseModels/IncomingCallPolicyOwnerRule.js.map +1 -1
  339. package/build/dist/Models/DatabaseModels/Index.js +20 -0
  340. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  341. package/build/dist/Models/DatabaseModels/Label.js +11 -0
  342. package/build/dist/Models/DatabaseModels/Label.js.map +1 -1
  343. package/build/dist/Models/DatabaseModels/LlmCostBudget.js +681 -0
  344. package/build/dist/Models/DatabaseModels/LlmCostBudget.js.map +1 -0
  345. package/build/dist/Models/DatabaseModels/LlmModelPrice.js +503 -0
  346. package/build/dist/Models/DatabaseModels/LlmModelPrice.js.map +1 -0
  347. package/build/dist/Models/DatabaseModels/LogSavedView.js +87 -0
  348. package/build/dist/Models/DatabaseModels/LogSavedView.js.map +1 -1
  349. package/build/dist/Models/DatabaseModels/MarketingConversion.js +224 -8
  350. package/build/dist/Models/DatabaseModels/MarketingConversion.js.map +1 -1
  351. package/build/dist/Models/DatabaseModels/MetricSavedView.js +59 -0
  352. package/build/dist/Models/DatabaseModels/MetricSavedView.js.map +1 -1
  353. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyLabelRule.js +36 -0
  354. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyLabelRule.js.map +1 -1
  355. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyOwnerRule.js +42 -0
  356. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyOwnerRule.js.map +1 -1
  357. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLabelRule.js +36 -0
  358. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLabelRule.js.map +1 -1
  359. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerRule.js +42 -0
  360. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerRule.js.map +1 -1
  361. package/build/dist/Models/DatabaseModels/Probe.js +21 -0
  362. package/build/dist/Models/DatabaseModels/Probe.js.map +1 -1
  363. package/build/dist/Models/DatabaseModels/RunbookLabelRule.js +36 -0
  364. package/build/dist/Models/DatabaseModels/RunbookLabelRule.js.map +1 -1
  365. package/build/dist/Models/DatabaseModels/RunbookOwnerRule.js +42 -0
  366. package/build/dist/Models/DatabaseModels/RunbookOwnerRule.js.map +1 -1
  367. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceMeasurement.js +1133 -0
  368. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceMeasurement.js.map +1 -0
  369. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceMeasurementValue.js +515 -0
  370. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceMeasurementValue.js.map +1 -0
  371. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js +9 -2
  372. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js.map +1 -1
  373. package/build/dist/Models/DatabaseModels/StatusPageLabelRule.js +36 -0
  374. package/build/dist/Models/DatabaseModels/StatusPageLabelRule.js.map +1 -1
  375. package/build/dist/Models/DatabaseModels/StatusPageOwnerRule.js +42 -0
  376. package/build/dist/Models/DatabaseModels/StatusPageOwnerRule.js.map +1 -1
  377. package/build/dist/Models/DatabaseModels/TableView.js +15 -0
  378. package/build/dist/Models/DatabaseModels/TableView.js.map +1 -1
  379. package/build/dist/Models/DatabaseModels/Team.js +13 -0
  380. package/build/dist/Models/DatabaseModels/Team.js.map +1 -1
  381. package/build/dist/Models/DatabaseModels/TeamMember.js +8 -0
  382. package/build/dist/Models/DatabaseModels/TeamMember.js.map +1 -1
  383. package/build/dist/Models/DatabaseModels/TraceSavedView.js +52 -0
  384. package/build/dist/Models/DatabaseModels/TraceSavedView.js.map +1 -1
  385. package/build/dist/Models/DatabaseModels/WorkflowLabelRule.js +36 -0
  386. package/build/dist/Models/DatabaseModels/WorkflowLabelRule.js.map +1 -1
  387. package/build/dist/Models/DatabaseModels/WorkflowOwnerRule.js +42 -0
  388. package/build/dist/Models/DatabaseModels/WorkflowOwnerRule.js.map +1 -1
  389. package/build/dist/Server/API/TelemetryAPI.js +136 -0
  390. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  391. package/build/dist/Server/API/UserAPI.js +75 -2
  392. package/build/dist/Server/API/UserAPI.js.map +1 -1
  393. package/build/dist/Server/EnvironmentConfig.js +29 -1
  394. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  395. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788000000000-AddDetectionRuleAndGoogleSecOpsConnection.js +36 -0
  396. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788000000000-AddDetectionRuleAndGoogleSecOpsConnection.js.map +1 -0
  397. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788100000000-AddMeasurements.js +162 -0
  398. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788100000000-AddMeasurements.js.map +1 -0
  399. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788200000000-AddLlmCostBudget.js +40 -0
  400. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788200000000-AddLlmCostBudget.js.map +1 -0
  401. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788300000000-AddLlmModelPrice.js +22 -0
  402. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788300000000-AddLlmModelPrice.js.map +1 -0
  403. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788400000000-AddMarketingConversionAttribution.js +52 -0
  404. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788400000000-AddMarketingConversionAttribution.js.map +1 -0
  405. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788500000000-RemoveLlmCostBudgetAlertColumns.js +38 -0
  406. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788500000000-RemoveLlmCostBudgetAlertColumns.js.map +1 -0
  407. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +12 -0
  408. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  409. package/build/dist/Server/Middleware/IdentityRateLimit.js +491 -0
  410. package/build/dist/Server/Middleware/IdentityRateLimit.js.map +1 -0
  411. package/build/dist/Server/Services/AccessTokenService.js +13 -2
  412. package/build/dist/Server/Services/AccessTokenService.js.map +1 -1
  413. package/build/dist/Server/Services/AlertMeasurementService.js +250 -0
  414. package/build/dist/Server/Services/AlertMeasurementService.js.map +1 -0
  415. package/build/dist/Server/Services/AlertMeasurementValueService.js +400 -0
  416. package/build/dist/Server/Services/AlertMeasurementValueService.js.map +1 -0
  417. package/build/dist/Server/Services/AlertService.js +37 -0
  418. package/build/dist/Server/Services/AlertService.js.map +1 -1
  419. package/build/dist/Server/Services/AlertStateTimelineService.js +48 -0
  420. package/build/dist/Server/Services/AlertStateTimelineService.js.map +1 -1
  421. package/build/dist/Server/Services/DetectionRuleService.js +50 -0
  422. package/build/dist/Server/Services/DetectionRuleService.js.map +1 -0
  423. package/build/dist/Server/Services/GoogleSecOpsConnectionService.js +55 -0
  424. package/build/dist/Server/Services/GoogleSecOpsConnectionService.js.map +1 -0
  425. package/build/dist/Server/Services/IncidentMeasurementService.js +252 -0
  426. package/build/dist/Server/Services/IncidentMeasurementService.js.map +1 -0
  427. package/build/dist/Server/Services/IncidentMeasurementValueService.js +413 -0
  428. package/build/dist/Server/Services/IncidentMeasurementValueService.js.map +1 -0
  429. package/build/dist/Server/Services/IncidentService.js +41 -0
  430. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  431. package/build/dist/Server/Services/IncidentStateTimelineService.js +50 -0
  432. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  433. package/build/dist/Server/Services/Index.js +30 -0
  434. package/build/dist/Server/Services/Index.js.map +1 -1
  435. package/build/dist/Server/Services/LlmCostBudgetService.js +89 -0
  436. package/build/dist/Server/Services/LlmCostBudgetService.js.map +1 -0
  437. package/build/dist/Server/Services/LlmModelPriceService.js +163 -0
  438. package/build/dist/Server/Services/LlmModelPriceService.js.map +1 -0
  439. package/build/dist/Server/Services/LogAggregationService.js +709 -0
  440. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  441. package/build/dist/Server/Services/LogCountBaselineService.js +112 -0
  442. package/build/dist/Server/Services/LogCountBaselineService.js.map +1 -0
  443. package/build/dist/Server/Services/MutableMetricService.js +28 -9
  444. package/build/dist/Server/Services/MutableMetricService.js.map +1 -1
  445. package/build/dist/Server/Services/ScheduledMaintenanceMeasurementService.js +254 -0
  446. package/build/dist/Server/Services/ScheduledMaintenanceMeasurementService.js.map +1 -0
  447. package/build/dist/Server/Services/ScheduledMaintenanceMeasurementValueService.js +434 -0
  448. package/build/dist/Server/Services/ScheduledMaintenanceMeasurementValueService.js.map +1 -0
  449. package/build/dist/Server/Services/ScheduledMaintenanceService.js +37 -0
  450. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  451. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +134 -0
  452. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
  453. package/build/dist/Server/Services/SecurityEventService.js +64 -0
  454. package/build/dist/Server/Services/SecurityEventService.js.map +1 -0
  455. package/build/dist/Server/Services/SpanCountBaselineService.js +122 -0
  456. package/build/dist/Server/Services/SpanCountBaselineService.js.map +1 -0
  457. package/build/dist/Server/Services/StatusPageService.js +4 -3
  458. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  459. package/build/dist/Server/Services/TelemetryUsageBillingService.js +15 -3
  460. package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
  461. package/build/dist/Server/Services/UserService.js +262 -42
  462. package/build/dist/Server/Services/UserService.js.map +1 -1
  463. package/build/dist/Server/Services/UserTotpAuthService.js +0 -73
  464. package/build/dist/Server/Services/UserTotpAuthService.js.map +1 -1
  465. package/build/dist/Server/Services/UserWebAuthnService.js +1 -73
  466. package/build/dist/Server/Services/UserWebAuthnService.js.map +1 -1
  467. package/build/dist/Server/Types/Billing/MeteredPlan/AllMeteredPlans.js +13 -0
  468. package/build/dist/Server/Types/Billing/MeteredPlan/AllMeteredPlans.js.map +1 -1
  469. package/build/dist/Server/Types/Database/Permissions/TenantPermission.js +10 -0
  470. package/build/dist/Server/Types/Database/Permissions/TenantPermission.js.map +1 -1
  471. package/build/dist/Server/Types/Database/QueryHelper.js +1 -1
  472. package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
  473. package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +1 -1
  474. package/build/dist/Server/Utils/AI/Toolbox/Index.js +3 -0
  475. package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -1
  476. package/build/dist/Server/Utils/AI/Toolbox/SecurityEventTools.js +257 -0
  477. package/build/dist/Server/Utils/AI/Toolbox/SecurityEventTools.js.map +1 -0
  478. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +13 -4
  479. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
  480. package/build/dist/Server/Utils/Attribution.js +77 -33
  481. package/build/dist/Server/Utils/Attribution.js.map +1 -1
  482. package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js +46 -9
  483. package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js.map +1 -1
  484. package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js +52 -8
  485. package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js.map +1 -1
  486. package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js +41 -17
  487. package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js.map +1 -1
  488. package/build/dist/Server/Utils/Marketing/Providers/Meta.js +42 -18
  489. package/build/dist/Server/Utils/Marketing/Providers/Meta.js.map +1 -1
  490. package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js +24 -11
  491. package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js.map +1 -1
  492. package/build/dist/Server/Utils/Marketing/Providers/Reddit.js +34 -15
  493. package/build/dist/Server/Utils/Marketing/Providers/Reddit.js.map +1 -1
  494. package/build/dist/Server/Utils/Measurement/MeasurementDefinitionValidator.js +109 -0
  495. package/build/dist/Server/Utils/Measurement/MeasurementDefinitionValidator.js.map +1 -0
  496. package/build/dist/Server/Utils/Measurement/MeasurementMetricWriter.js +131 -0
  497. package/build/dist/Server/Utils/Measurement/MeasurementMetricWriter.js.map +1 -0
  498. package/build/dist/Server/Utils/Monitor/Criteria/CountAnomaly.js +111 -0
  499. package/build/dist/Server/Utils/Monitor/Criteria/CountAnomaly.js.map +1 -0
  500. package/build/dist/Server/Utils/Monitor/Criteria/LogMonitorCriteria.js +84 -1
  501. package/build/dist/Server/Utils/Monitor/Criteria/LogMonitorCriteria.js.map +1 -1
  502. package/build/dist/Server/Utils/Monitor/Criteria/SecurityEventsMonitorCriteria.js +35 -0
  503. package/build/dist/Server/Utils/Monitor/Criteria/SecurityEventsMonitorCriteria.js.map +1 -0
  504. package/build/dist/Server/Utils/Monitor/Criteria/TraceMonitorCriteria.js +84 -1
  505. package/build/dist/Server/Utils/Monitor/Criteria/TraceMonitorCriteria.js.map +1 -1
  506. package/build/dist/Server/Utils/Monitor/MonitorCriteriaDataExtractor.js +7 -0
  507. package/build/dist/Server/Utils/Monitor/MonitorCriteriaDataExtractor.js.map +1 -1
  508. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +12 -0
  509. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  510. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +9 -0
  511. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
  512. package/build/dist/Server/Utils/Monitor/MonitorResource.js +10 -0
  513. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  514. package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js +6 -5
  515. package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js.map +1 -1
  516. package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js +1 -0
  517. package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js.map +1 -1
  518. package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js +404 -0
  519. package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js.map +1 -0
  520. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js +145 -0
  521. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js.map +1 -0
  522. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js +176 -0
  523. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js.map +1 -0
  524. package/build/dist/Server/Utils/SecurityEvent/SecurityEventRow.js +69 -0
  525. package/build/dist/Server/Utils/SecurityEvent/SecurityEventRow.js.map +1 -0
  526. package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js +493 -0
  527. package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js.map +1 -0
  528. package/build/dist/Server/Utils/Telemetry/LlmCostBudgetEvaluator.js +317 -0
  529. package/build/dist/Server/Utils/Telemetry/LlmCostBudgetEvaluator.js.map +1 -0
  530. package/build/dist/Server/Utils/Telemetry/LlmSpan.js +56 -4
  531. package/build/dist/Server/Utils/Telemetry/LlmSpan.js.map +1 -1
  532. package/build/dist/Server/Utils/Telemetry/LogErrorPatternSql.js +112 -0
  533. package/build/dist/Server/Utils/Telemetry/LogErrorPatternSql.js.map +1 -0
  534. package/build/dist/Server/Utils/UserPermission/UserPermission.js +46 -1
  535. package/build/dist/Server/Utils/UserPermission/UserPermission.js.map +1 -1
  536. package/build/dist/Types/AI/AIChatTypes.js +1 -0
  537. package/build/dist/Types/AI/AIChatTypes.js.map +1 -1
  538. package/build/dist/Types/Alerts/AlertMeasurementAnchorType.js +15 -0
  539. package/build/dist/Types/Alerts/AlertMeasurementAnchorType.js.map +1 -0
  540. package/build/dist/Types/Alerts/AlertStateRole.js +12 -0
  541. package/build/dist/Types/Alerts/AlertStateRole.js.map +1 -0
  542. package/build/dist/Types/AnalyticsDatabase/AnalyticsTableName.js +16 -0
  543. package/build/dist/Types/AnalyticsDatabase/AnalyticsTableName.js.map +1 -1
  544. package/build/dist/Types/Dashboard/Chart/ChartType.js +1 -0
  545. package/build/dist/Types/Dashboard/Chart/ChartType.js.map +1 -1
  546. package/build/dist/Types/Dashboard/DashboardComponentType.js +2 -0
  547. package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
  548. package/build/dist/Types/Dashboard/DashboardComponents/DashboardSecurityEventsFlowComponent.js +2 -0
  549. package/build/dist/Types/Dashboard/DashboardComponents/DashboardSecurityEventsFlowComponent.js.map +1 -0
  550. package/build/dist/Types/Dashboard/DashboardComponents/DashboardSecurityEventsListComponent.js +2 -0
  551. package/build/dist/Types/Dashboard/DashboardComponents/DashboardSecurityEventsListComponent.js.map +1 -0
  552. package/build/dist/Types/Incident/IncidentMeasurementAnchorType.js +29 -0
  553. package/build/dist/Types/Incident/IncidentMeasurementAnchorType.js.map +1 -0
  554. package/build/dist/Types/Incident/IncidentStateRole.js +15 -0
  555. package/build/dist/Types/Incident/IncidentStateRole.js.map +1 -0
  556. package/build/dist/Types/Marketing/Attribution.js +68 -0
  557. package/build/dist/Types/Marketing/Attribution.js.map +1 -0
  558. package/build/dist/Types/Marketing/MarketingConversion.js +36 -7
  559. package/build/dist/Types/Marketing/MarketingConversion.js.map +1 -1
  560. package/build/dist/Types/Measurement/MeasurementAggregationType.js +40 -0
  561. package/build/dist/Types/Measurement/MeasurementAggregationType.js.map +1 -0
  562. package/build/dist/Types/Measurement/MeasurementAnchorKind.js +22 -0
  563. package/build/dist/Types/Measurement/MeasurementAnchorKind.js.map +1 -0
  564. package/build/dist/Types/Measurement/MeasurementOccurrence.js +15 -0
  565. package/build/dist/Types/Measurement/MeasurementOccurrence.js.map +1 -0
  566. package/build/dist/Types/Measurement/MeasurementStatus.js +32 -0
  567. package/build/dist/Types/Measurement/MeasurementStatus.js.map +1 -0
  568. package/build/dist/Types/MeteredPlan/ProductType.js +1 -0
  569. package/build/dist/Types/MeteredPlan/ProductType.js.map +1 -1
  570. package/build/dist/Types/Monitor/CriteriaFilter.js +2 -0
  571. package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
  572. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +59 -0
  573. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
  574. package/build/dist/Types/Monitor/MonitorStep.js +17 -0
  575. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  576. package/build/dist/Types/Monitor/MonitorStepSecurityEventsMonitor.js +64 -0
  577. package/build/dist/Types/Monitor/MonitorStepSecurityEventsMonitor.js.map +1 -0
  578. package/build/dist/Types/Monitor/MonitorType.js +19 -0
  579. package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
  580. package/build/dist/Types/Monitor/SecurityEventsMonitor/SecurityEventsMonitorResponse.js +2 -0
  581. package/build/dist/Types/Monitor/SecurityEventsMonitor/SecurityEventsMonitorResponse.js.map +1 -0
  582. package/build/dist/Types/Permission.js +347 -2
  583. package/build/dist/Types/Permission.js.map +1 -1
  584. package/build/dist/Types/ScheduledMaintenance/ScheduledMaintenanceMeasurementAnchorType.js +19 -0
  585. package/build/dist/Types/ScheduledMaintenance/ScheduledMaintenanceMeasurementAnchorType.js.map +1 -0
  586. package/build/dist/Types/ScheduledMaintenance/ScheduledMaintenanceStateRole.js +17 -0
  587. package/build/dist/Types/ScheduledMaintenance/ScheduledMaintenanceStateRole.js.map +1 -0
  588. package/build/dist/Types/SecurityEvent/NormalizedSecurityEvent.js +2 -0
  589. package/build/dist/Types/SecurityEvent/NormalizedSecurityEvent.js.map +1 -0
  590. package/build/dist/Types/SecurityEvent/OcsfEventClass.js +181 -0
  591. package/build/dist/Types/SecurityEvent/OcsfEventClass.js.map +1 -0
  592. package/build/dist/Types/SecurityEvent/OcsfSeverity.js +81 -0
  593. package/build/dist/Types/SecurityEvent/OcsfSeverity.js.map +1 -0
  594. package/build/dist/Types/SecurityEvent/SecurityEventFormat.js +21 -0
  595. package/build/dist/Types/SecurityEvent/SecurityEventFormat.js.map +1 -0
  596. package/build/dist/Types/SecurityEvent/SigmaRule.js +16 -0
  597. package/build/dist/Types/SecurityEvent/SigmaRule.js.map +1 -0
  598. package/build/dist/Types/Telemetry/LlmConventions.js +19 -1
  599. package/build/dist/Types/Telemetry/LlmConventions.js.map +1 -1
  600. package/build/dist/Types/Telemetry/LlmCostCatalog.js +484 -0
  601. package/build/dist/Types/Telemetry/LlmCostCatalog.js.map +1 -0
  602. package/build/dist/Types/Telemetry/ServiceType.js +1 -0
  603. package/build/dist/Types/Telemetry/ServiceType.js.map +1 -1
  604. package/build/dist/Types/Telemetry/TelemetryRetentionConfig.js.map +1 -1
  605. package/build/dist/Types/Telemetry/TelemetryType.js +1 -0
  606. package/build/dist/Types/Telemetry/TelemetryType.js.map +1 -1
  607. package/build/dist/Types/TwoFactorAuthStatus.js +41 -0
  608. package/build/dist/Types/TwoFactorAuthStatus.js.map +1 -0
  609. package/build/dist/UI/Components/BulkUpdate/BulkArchiveActions.js +16 -2
  610. package/build/dist/UI/Components/BulkUpdate/BulkArchiveActions.js.map +1 -1
  611. package/build/dist/UI/Components/BulkUpdate/BulkLabelActions.js +15 -1
  612. package/build/dist/UI/Components/BulkUpdate/BulkLabelActions.js.map +1 -1
  613. package/build/dist/UI/Components/BulkUpdate/BulkOwnerActions.js +20 -1
  614. package/build/dist/UI/Components/BulkUpdate/BulkOwnerActions.js.map +1 -1
  615. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +1 -1
  616. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
  617. package/build/dist/UI/Components/Button/Button.js +22 -1
  618. package/build/dist/UI/Components/Button/Button.js.map +1 -1
  619. package/build/dist/UI/Components/Card/Card.js +1 -1
  620. package/build/dist/UI/Components/Card/Card.js.map +1 -1
  621. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +14 -1
  622. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
  623. package/build/dist/UI/Components/Dictionary/DictionaryFilterOperator.js +47 -0
  624. package/build/dist/UI/Components/Dictionary/DictionaryFilterOperator.js.map +1 -1
  625. package/build/dist/UI/Components/DuplicateModel/DuplicateModel.js +8 -0
  626. package/build/dist/UI/Components/DuplicateModel/DuplicateModel.js.map +1 -1
  627. package/build/dist/UI/Components/Forms/ModelForm.js +40 -1
  628. package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
  629. package/build/dist/UI/Components/List/ListRow.js +4 -1
  630. package/build/dist/UI/Components/List/ListRow.js.map +1 -1
  631. package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js +32 -7
  632. package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js.map +1 -1
  633. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +2 -17
  634. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
  635. package/build/dist/UI/Components/LogsViewer/components/SavedViewsDropdown.js +93 -4
  636. package/build/dist/UI/Components/LogsViewer/components/SavedViewsDropdown.js.map +1 -1
  637. package/build/dist/UI/Components/ModelDelete/ModelDelete.js +13 -0
  638. package/build/dist/UI/Components/ModelDelete/ModelDelete.js.map +1 -1
  639. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +27 -11
  640. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
  641. package/build/dist/UI/Components/ModelList/ModelList.js +13 -2
  642. package/build/dist/UI/Components/ModelList/ModelList.js.map +1 -1
  643. package/build/dist/UI/Components/ModelList/StaticModelList.js +4 -1
  644. package/build/dist/UI/Components/ModelList/StaticModelList.js.map +1 -1
  645. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +101 -40
  646. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  647. package/build/dist/UI/Components/ModelTable/ModelTable.js +9 -7
  648. package/build/dist/UI/Components/ModelTable/ModelTable.js.map +1 -1
  649. package/build/dist/UI/Components/ModelTable/TableView.js +32 -4
  650. package/build/dist/UI/Components/ModelTable/TableView.js.map +1 -1
  651. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +19 -2
  652. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
  653. package/build/dist/UI/Components/OrderedStatesList/Item.js +4 -1
  654. package/build/dist/UI/Components/OrderedStatesList/Item.js.map +1 -1
  655. package/build/dist/UI/Components/OrderedStatesList/OrderedStatesList.js +33 -32
  656. package/build/dist/UI/Components/OrderedStatesList/OrderedStatesList.js.map +1 -1
  657. package/build/dist/UI/Components/ResetObjectID/ResetObjectID.js +8 -0
  658. package/build/dist/UI/Components/ResetObjectID/ResetObjectID.js.map +1 -1
  659. package/build/dist/UI/Components/SavedViews/SavedViewsFacetSection.js +98 -0
  660. package/build/dist/UI/Components/SavedViews/SavedViewsFacetSection.js.map +1 -0
  661. package/build/dist/UI/Components/SavedViews/SavedViewsList.js +99 -0
  662. package/build/dist/UI/Components/SavedViews/SavedViewsList.js.map +1 -0
  663. package/build/dist/UI/Components/SavedViews/SavedViewsSearchInput.js +17 -0
  664. package/build/dist/UI/Components/SavedViews/SavedViewsSearchInput.js.map +1 -0
  665. package/build/dist/UI/Components/SavedViews/SavedViewsShowMoreButton.js +15 -0
  666. package/build/dist/UI/Components/SavedViews/SavedViewsShowMoreButton.js.map +1 -0
  667. package/build/dist/UI/Components/Table/TableRow.js +8 -2
  668. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  669. package/build/dist/UI/Components/TelemetryViewer/components/SavedViewsDropdown.js +93 -4
  670. package/build/dist/UI/Components/TelemetryViewer/components/SavedViewsDropdown.js.map +1 -1
  671. package/build/dist/UI/Types/UseComponentOutsideClick.js +39 -0
  672. package/build/dist/UI/Types/UseComponentOutsideClick.js.map +1 -1
  673. package/build/dist/UI/Utils/PermissionGate.js +164 -0
  674. package/build/dist/UI/Utils/PermissionGate.js.map +1 -0
  675. package/build/dist/Utils/Dashboard/Components/DashboardSecurityEventsFlowComponent.js +70 -0
  676. package/build/dist/Utils/Dashboard/Components/DashboardSecurityEventsFlowComponent.js.map +1 -0
  677. package/build/dist/Utils/Dashboard/Components/DashboardSecurityEventsListComponent.js +92 -0
  678. package/build/dist/Utils/Dashboard/Components/DashboardSecurityEventsListComponent.js.map +1 -0
  679. package/build/dist/Utils/Dashboard/Components/Index.js +8 -0
  680. package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
  681. package/build/dist/Utils/Measurement/MeasurementEvaluator.js +210 -0
  682. package/build/dist/Utils/Measurement/MeasurementEvaluator.js.map +1 -0
  683. package/build/dist/Utils/Monitor/MonitorSummarySnapshotUtil.js +1 -0
  684. package/build/dist/Utils/Monitor/MonitorSummarySnapshotUtil.js.map +1 -1
  685. package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js +51 -10
  686. package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js.map +1 -1
  687. package/build/dist/Utils/NetworkDevice/DeviceHealthStateUtil.js +108 -0
  688. package/build/dist/Utils/NetworkDevice/DeviceHealthStateUtil.js.map +1 -0
  689. package/build/dist/Utils/SecurityEvent/GenericNormalizer.js +176 -0
  690. package/build/dist/Utils/SecurityEvent/GenericNormalizer.js.map +1 -0
  691. package/build/dist/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.js +146 -0
  692. package/build/dist/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.js.map +1 -0
  693. package/build/dist/Utils/SecurityEvent/NormalizerHelpers.js +196 -0
  694. package/build/dist/Utils/SecurityEvent/NormalizerHelpers.js.map +1 -0
  695. package/build/dist/Utils/SecurityEvent/OcsfNormalizer.js +136 -0
  696. package/build/dist/Utils/SecurityEvent/OcsfNormalizer.js.map +1 -0
  697. package/build/dist/Utils/SecurityEvent/SecurityEventNormalizer.js +64 -0
  698. package/build/dist/Utils/SecurityEvent/SecurityEventNormalizer.js.map +1 -0
  699. package/build/dist/Utils/SecurityEvent/Sigma/SigmaRuleParser.js +388 -0
  700. package/build/dist/Utils/SecurityEvent/Sigma/SigmaRuleParser.js.map +1 -0
  701. package/build/dist/Utils/SecurityEvent/UdmNormalizer.js +328 -0
  702. package/build/dist/Utils/SecurityEvent/UdmNormalizer.js.map +1 -0
  703. package/build/dist/Utils/Telemetry/CrossSignalScope.js +29 -7
  704. package/build/dist/Utils/Telemetry/CrossSignalScope.js.map +1 -1
  705. package/build/dist/Utils/Telemetry/LogErrorPattern.js +284 -0
  706. package/build/dist/Utils/Telemetry/LogErrorPattern.js.map +1 -0
  707. package/package.json +3 -1
@@ -0,0 +1,1190 @@
1
+ import DatabaseService from "../../../Server/Services/DatabaseService";
2
+ import UserService from "../../../Server/Services/UserService";
3
+ import UserSessionService from "../../../Server/Services/UserSessionService";
4
+ import UserTotpAuthService from "../../../Server/Services/UserTotpAuthService";
5
+ import UserWebAuthnService from "../../../Server/Services/UserWebAuthnService";
6
+ import logger from "../../../Server/Utils/Logger";
7
+ import User from "../../../Models/DatabaseModels/User";
8
+ import ColumnLength from "../../../Types/Database/ColumnLength";
9
+ import LIMIT_MAX from "../../../Types/Database/LimitMax";
10
+ import Email from "../../../Types/Email";
11
+ import BadDataException from "../../../Types/Exception/BadDataException";
12
+ import NotFoundException from "../../../Types/Exception/NotFoundException";
13
+ import HashedString from "../../../Types/HashedString";
14
+ import ObjectID from "../../../Types/ObjectID";
15
+ import { getJestSpyOn } from "../../Spy";
16
+ import {
17
+ afterEach,
18
+ beforeEach,
19
+ describe,
20
+ expect,
21
+ jest,
22
+ test,
23
+ } from "@jest/globals";
24
+
25
+ /*
26
+ * ---------------------------------------------------------------------------
27
+ * UserService.setTwoFactorAuthRequired / resetTwoFactorAuth, and the three
28
+ * hooks that had to be DELETED for them to work at all.
29
+ *
30
+ * These are the two write paths behind Admin Dashboard > Users > (a user) >
31
+ * Authentication, plus the bulk actions on the user list. Everything here is a
32
+ * property that can regress while the page still renders and the request still
33
+ * returns 200:
34
+ *
35
+ * - "require two factor auth" is a promise about who can sign in. If the
36
+ * session revocation stops happening, or stops being awaited, or its reason
37
+ * string outgrows the ShortText column it is written into, the flag is set
38
+ * and every already-signed-in session sails on refreshing itself
39
+ * indefinitely. The page says "Required". Nothing says otherwise.
40
+ * - "reset two factor auth" is the lost-device fix. If it also cleared
41
+ * `enableTwoFactorAuth`, an operator helping with a lost phone would
42
+ * silently take the account OFF two factor auth, and the request that asked
43
+ * for a fresh QR code would have quietly downgraded the account instead.
44
+ * - three hooks used to refuse exactly the states this feature creates on
45
+ * purpose. They are gone, and "gone" is a thing tests have to hold in place:
46
+ * each of them looked like a safety guard, so each of them is a plausible
47
+ * thing for a future reader to put back.
48
+ *
49
+ * Sibling file: UserAuthenticationService.test.ts already covers
50
+ * getAuthenticationStatus (including the tri-state), setPassword and
51
+ * sendPasswordResetLink. Nothing here duplicates that -- this file is about the
52
+ * two mutating methods and the removed hooks.
53
+ *
54
+ * Everything stubs at this service's own boundary -- findOneBy / updateOneById
55
+ * / findBy on the UserService singleton, plus the two authenticator services
56
+ * and UserSessionService -- so the assertions are about what these methods ASK
57
+ * for. No database is involved.
58
+ * ---------------------------------------------------------------------------
59
+ */
60
+
61
+ /*
62
+ * A spy plus the name to blame in a violation message, so a failure says WHICH
63
+ * write entry point was used rather than only that one of them was.
64
+ */
65
+ type NamedSpy = {
66
+ name: string;
67
+ spy: any;
68
+ };
69
+
70
+ type BuildUserFunction = (data: {
71
+ id: ObjectID;
72
+ enableTwoFactorAuth?: boolean | undefined;
73
+ email?: string | undefined;
74
+ }) => User;
75
+
76
+ const buildUser: BuildUserFunction = (data: {
77
+ id: ObjectID;
78
+ enableTwoFactorAuth?: boolean | undefined;
79
+ email?: string | undefined;
80
+ }): User => {
81
+ const user: User = new User();
82
+ user.id = data.id;
83
+
84
+ /*
85
+ * Assigned only when supplied, so an omitted column is genuinely absent --
86
+ * the shape a `select` that did not ask for it produces -- rather than
87
+ * explicitly undefined.
88
+ */
89
+ if (data.enableTwoFactorAuth !== undefined) {
90
+ user.enableTwoFactorAuth = data.enableTwoFactorAuth;
91
+ }
92
+
93
+ if (data.email !== undefined) {
94
+ user.email = new Email(data.email);
95
+ }
96
+
97
+ return user;
98
+ };
99
+
100
+ describe("UserService -- admin-controlled two factor auth", () => {
101
+ /*
102
+ * TWO DISTINCT IDS ON PURPOSE, and the distinction is the only thing keeping
103
+ * a whole family of assertions in this file honest.
104
+ *
105
+ * `userId` is what the CALLER asked about -- the id in the admin route's
106
+ * URL. `foundUserId` is the id on the row `findOneBy` actually resolves.
107
+ * Both methods under test look the row up and then key every subsequent
108
+ * operation on `user.id!`, never on `data.userId`, which is what stops a
109
+ * lookup that resolves something else (a soft-delete filter, a tenancy
110
+ * scope, a future `findOneBy` overload) from ending with an update, a
111
+ * delete, or a session revocation aimed at an id nobody confirmed exists.
112
+ *
113
+ * If the fixture made these two the same value -- the obvious way to write
114
+ * it -- then `updateOneById({ id: data.userId })` and `updateOneById({ id:
115
+ * user.id! })` would be indistinguishable, and every "against the right
116
+ * user" assertion below would pass for either. They are generated
117
+ * separately so that they cannot be.
118
+ */
119
+ let userId: ObjectID;
120
+ let foundUserId: ObjectID;
121
+ let findOneBySpy: any;
122
+ let findBySpy: any;
123
+ let updateOneByIdSpy: any;
124
+ let updateBySpy: any;
125
+ let revokeSessionsSpy: any;
126
+ let totpDeleteBySpy: any;
127
+ let webAuthnDeleteBySpy: any;
128
+ let totpCountBySpy: any;
129
+ let webAuthnCountBySpy: any;
130
+ let totpFindBySpy: any;
131
+ let webAuthnFindBySpy: any;
132
+ let loggerInfoSpy: any;
133
+
134
+ beforeEach(() => {
135
+ jest.restoreAllMocks();
136
+
137
+ userId = ObjectID.generate();
138
+ foundUserId = ObjectID.generate();
139
+
140
+ findOneBySpy = getJestSpyOn(UserService, "findOneBy").mockImplementation(
141
+ async (): Promise<User | null> => {
142
+ return null;
143
+ },
144
+ );
145
+
146
+ /*
147
+ * Returns ONE row, not an empty list, and that is load-bearing rather than
148
+ * incidental.
149
+ *
150
+ * The guard this suite exists to prove is gone read its users through
151
+ * `findBy` and threw from INSIDE `for (const user of users)`. Stub that to
152
+ * `[]` and a verbatim restoration of the guard iterates zero times, throws
153
+ * nothing, and every "the guard stays deleted" test below stays green
154
+ * while the feature is dead. A non-empty list is what makes those tests
155
+ * able to fail.
156
+ */
157
+ findBySpy = getJestSpyOn(UserService, "findBy").mockImplementation(
158
+ async (): Promise<Array<User>> => {
159
+ return [buildUser({ id: foundUserId })];
160
+ },
161
+ );
162
+
163
+ updateOneByIdSpy = getJestSpyOn(
164
+ UserService,
165
+ "updateOneById",
166
+ ).mockImplementation(async (): Promise<void> => {
167
+ return undefined;
168
+ });
169
+
170
+ /*
171
+ * The OTHER write entry point on this service. `updateOneById` is the one
172
+ * these methods use today, but `updateBy` is what it funnels into, so
173
+ * spying on both is what lets "this method writes nothing to the User row"
174
+ * be asserted as a fact about the service rather than about one method
175
+ * name -- see the reset case below.
176
+ */
177
+ updateBySpy = getJestSpyOn(UserService, "updateBy").mockImplementation(
178
+ async (): Promise<number> => {
179
+ return 0;
180
+ },
181
+ );
182
+
183
+ revokeSessionsSpy = getJestSpyOn(
184
+ UserSessionService,
185
+ "revokeAllSessionsByUserId",
186
+ ).mockImplementation(async (): Promise<void> => {
187
+ return undefined;
188
+ });
189
+
190
+ totpDeleteBySpy = getJestSpyOn(
191
+ UserTotpAuthService,
192
+ "deleteBy",
193
+ ).mockImplementation(async (): Promise<number> => {
194
+ return 0;
195
+ });
196
+
197
+ webAuthnDeleteBySpy = getJestSpyOn(
198
+ UserWebAuthnService,
199
+ "deleteBy",
200
+ ).mockImplementation(async (): Promise<number> => {
201
+ return 0;
202
+ });
203
+
204
+ /*
205
+ * The two probes the DELETED guards used to make. They are stubbed rather
206
+ * than left real so that "was this consulted at all?" is answerable, which
207
+ * is how the removal is held in place: a guard that came back would have to
208
+ * ask one of these two services how many verified factors the user has.
209
+ */
210
+ totpCountBySpy = getJestSpyOn(UserTotpAuthService, "countBy");
211
+ webAuthnCountBySpy = getJestSpyOn(UserWebAuthnService, "countBy");
212
+ totpFindBySpy = getJestSpyOn(UserTotpAuthService, "findBy");
213
+ webAuthnFindBySpy = getJestSpyOn(UserWebAuthnService, "findBy");
214
+
215
+ for (const spy of [
216
+ totpCountBySpy,
217
+ webAuthnCountBySpy,
218
+ totpFindBySpy,
219
+ webAuthnFindBySpy,
220
+ ]) {
221
+ spy.mockImplementation(async (): Promise<any> => {
222
+ return undefined;
223
+ });
224
+ }
225
+
226
+ loggerInfoSpy = getJestSpyOn(logger, "info").mockImplementation(
227
+ (): void => {
228
+ return undefined;
229
+ },
230
+ );
231
+
232
+ /*
233
+ * The deliberate-failure cases below hand a rejection to the CaptureSpan
234
+ * decorator, which records it. That is correct behaviour, and it is also a
235
+ * stack trace on stderr for a test that passed -- silenced so a genuine
236
+ * error in this suite's output is still worth reading.
237
+ */
238
+ getJestSpyOn(logger, "error").mockImplementation((): void => {
239
+ return undefined;
240
+ });
241
+ });
242
+
243
+ afterEach(() => {
244
+ jest.restoreAllMocks();
245
+ });
246
+
247
+ type ResolveUserFunction = (user: User | null) => void;
248
+
249
+ // Makes the next (and every subsequent) findOneBy return this row.
250
+ const resolveUser: ResolveUserFunction = (user: User | null): void => {
251
+ findOneBySpy.mockImplementation(async (): Promise<User | null> => {
252
+ return user;
253
+ });
254
+ };
255
+
256
+ type FlushMicrotasksFunction = () => Promise<void>;
257
+
258
+ /*
259
+ * Lets every already-scheduled continuation run without advancing anything
260
+ * that is genuinely blocked. Used by the "is it awaited?" cases: after this,
261
+ * a method that did NOT await its dependency has definitely finished.
262
+ */
263
+ const flushMicrotasks: FlushMicrotasksFunction = async (): Promise<void> => {
264
+ await new Promise<void>((resolve: () => void) => {
265
+ setTimeout(resolve, 0);
266
+ });
267
+ };
268
+
269
+ type AuthenticatorProbeSpiesFunction = () => Array<any>;
270
+
271
+ const authenticatorProbeSpies: AuthenticatorProbeSpiesFunction =
272
+ (): Array<any> => {
273
+ return [
274
+ totpCountBySpy,
275
+ webAuthnCountBySpy,
276
+ totpFindBySpy,
277
+ webAuthnFindBySpy,
278
+ ];
279
+ };
280
+
281
+ describe("setTwoFactorAuthRequired", () => {
282
+ test("enabling writes exactly one column: enableTwoFactorAuth true", async () => {
283
+ /*
284
+ * The key set is pinned rather than only the value, so a later edit
285
+ * cannot smuggle another column into an update whose URL says it is
286
+ * about one flag. This method runs as root, which means every column on
287
+ * `User` is writable through it -- isMasterAdmin, email, password -- and
288
+ * a stray key here would be an admin route that changes something its
289
+ * name does not mention.
290
+ */
291
+ resolveUser(buildUser({ id: foundUserId, enableTwoFactorAuth: false }));
292
+
293
+ await UserService.setTwoFactorAuthRequired({
294
+ userId: userId,
295
+ isRequired: true,
296
+ });
297
+
298
+ expect(updateOneByIdSpy).toHaveBeenCalledTimes(1);
299
+
300
+ const call: any = updateOneByIdSpy.mock.calls[0][0];
301
+
302
+ expect(Object.keys(call.data)).toEqual(["enableTwoFactorAuth"]);
303
+ expect(call.data.enableTwoFactorAuth).toBe(true);
304
+ });
305
+
306
+ test("disabling writes exactly one column: enableTwoFactorAuth false", async () => {
307
+ /*
308
+ * The mirror, so the test above cannot be satisfied by hard-coding true.
309
+ * `false` has to be WRITTEN, not omitted -- an update that simply left the
310
+ * key off would leave the requirement in place while the page reported it
311
+ * lifted.
312
+ */
313
+ resolveUser(buildUser({ id: foundUserId, enableTwoFactorAuth: true }));
314
+
315
+ await UserService.setTwoFactorAuthRequired({
316
+ userId: userId,
317
+ isRequired: false,
318
+ });
319
+
320
+ expect(updateOneByIdSpy).toHaveBeenCalledTimes(1);
321
+
322
+ const call: any = updateOneByIdSpy.mock.calls[0][0];
323
+
324
+ expect(Object.keys(call.data)).toEqual(["enableTwoFactorAuth"]);
325
+ expect(call.data.enableTwoFactorAuth).toBe(false);
326
+ });
327
+
328
+ test("writes as root, against the id that was actually found", async () => {
329
+ /*
330
+ * Two separate facts. Root, because a master admin acts outside any
331
+ * project and `enableTwoFactorAuth` has an empty update ACL -- without it
332
+ * the write is refused for everyone. And against the FOUND row's id
333
+ * rather than the requested one, so a lookup that ever resolves something
334
+ * else (a soft-delete filter, a tenancy scope) cannot end with this method
335
+ * writing to an id it never confirmed exists.
336
+ *
337
+ * The second fact only exists because the lookup here resolves a row
338
+ * whose id is NOT the requested one. With a fixture that returned the
339
+ * requested id back, `id: data.userId` and `id: user.id!` would produce
340
+ * byte-identical calls and this assertion would be decoration.
341
+ */
342
+ resolveUser(buildUser({ id: foundUserId, enableTwoFactorAuth: false }));
343
+
344
+ await UserService.setTwoFactorAuthRequired({
345
+ userId: userId,
346
+ isRequired: true,
347
+ });
348
+
349
+ const call: any = updateOneByIdSpy.mock.calls[0][0];
350
+ const violations: Array<string> = [];
351
+
352
+ if (call.id.toString() !== foundUserId.toString()) {
353
+ violations.push(
354
+ `updateOneById targeted ${call.id.toString()}, not the found user ${foundUserId.toString()}`,
355
+ );
356
+ }
357
+
358
+ if (call.props.isRoot !== true) {
359
+ violations.push(
360
+ "updateOneById did not run with props { isRoot: true }",
361
+ );
362
+ }
363
+
364
+ expect(violations).toEqual([]);
365
+
366
+ // The lookup itself is a root read of the requested id.
367
+ expect(findOneBySpy.mock.calls[0][0].query).toEqual({ _id: userId });
368
+ expect(findOneBySpy.mock.calls[0][0].props).toEqual({ isRoot: true });
369
+ });
370
+
371
+ test("ENABLING revokes every session, with a reason short enough to store", async () => {
372
+ /*
373
+ * Two failures in one place, and the second is invisible until it fires.
374
+ *
375
+ * `getUserMiddleware` validates an access token statelessly and never
376
+ * looks at `UserSession`, so somebody already signed in keeps working for
377
+ * the rest of that token's life whatever is written here. Revoking is what
378
+ * stops POST /refresh-token -- which DOES check `isRevoked` -- from
379
+ * renewing it forever. Without it, "require two factor auth" is a promise
380
+ * the product does not keep for anybody currently signed in.
381
+ *
382
+ * And `UserSession.revokedReason` is a ShortText column. A reason longer
383
+ * than that fails the very UPDATE that is supposed to be closing the
384
+ * window, so a well-meant, more descriptive string would turn a security
385
+ * action into an exception -- asserted against ColumnLength itself rather
386
+ * than a copied number, so a change to the column moves this with it.
387
+ */
388
+ resolveUser(buildUser({ id: foundUserId, enableTwoFactorAuth: false }));
389
+
390
+ await UserService.setTwoFactorAuthRequired({
391
+ userId: userId,
392
+ isRequired: true,
393
+ });
394
+
395
+ expect(revokeSessionsSpy).toHaveBeenCalledTimes(1);
396
+
397
+ const revokedUserId: any = revokeSessionsSpy.mock.calls[0][0];
398
+ const options: any = revokeSessionsSpy.mock.calls[0][1];
399
+
400
+ expect(revokedUserId.toString()).toBe(foundUserId.toString());
401
+
402
+ const reason: string = options.reason as string;
403
+
404
+ expect(typeof reason).toBe("string");
405
+ expect(reason.length).toBeGreaterThan(0);
406
+ expect(reason.length).toBeLessThanOrEqual(ColumnLength.ShortText);
407
+ });
408
+
409
+ test("DISABLING does not revoke sessions", async () => {
410
+ /*
411
+ * The asymmetry is deliberate, not an oversight. Relaxing a requirement
412
+ * opens no window -- nobody's access got wider because they were asked for
413
+ * LESS -- and signing the whole company out to give them less to do would
414
+ * be gratuitous. Somebody "tidying up" this method into a symmetric
415
+ * revoke-either-way would log every user of a project out for a change
416
+ * that costs them nothing.
417
+ */
418
+ resolveUser(buildUser({ id: foundUserId, enableTwoFactorAuth: true }));
419
+
420
+ await UserService.setTwoFactorAuthRequired({
421
+ userId: userId,
422
+ isRequired: false,
423
+ });
424
+
425
+ expect(updateOneByIdSpy).toHaveBeenCalledTimes(1);
426
+ expect(revokeSessionsSpy).not.toHaveBeenCalled();
427
+ });
428
+
429
+ test("does not resolve until the revocation has actually settled", async () => {
430
+ /*
431
+ * A fire-and-forget revocation would still call the spy, so every
432
+ * assertion above would keep passing while the guarantee evaporated: the
433
+ * route would answer 200, the UI would say "required", and the revocation
434
+ * could fail afterwards with nobody listening. Worse, the admin's next
435
+ * action (a reset, say) would race a revocation still in flight.
436
+ *
437
+ * So the await is pinned directly, by blocking the revocation on a gate
438
+ * this test holds and checking the method has not finished.
439
+ */
440
+ resolveUser(buildUser({ id: foundUserId, enableTwoFactorAuth: false }));
441
+
442
+ let releaseRevocation: () => void = (): void => {
443
+ return undefined;
444
+ };
445
+
446
+ const revocationGate: Promise<void> = new Promise<void>(
447
+ (resolve: () => void) => {
448
+ releaseRevocation = resolve;
449
+ },
450
+ );
451
+
452
+ revokeSessionsSpy.mockImplementation(async (): Promise<void> => {
453
+ await revocationGate;
454
+ });
455
+
456
+ let settled: boolean = false;
457
+
458
+ const pending: Promise<void> = UserService.setTwoFactorAuthRequired({
459
+ userId: userId,
460
+ isRequired: true,
461
+ }).then((): void => {
462
+ settled = true;
463
+ });
464
+
465
+ await flushMicrotasks();
466
+
467
+ expect(revokeSessionsSpy).toHaveBeenCalledTimes(1);
468
+ expect(settled).toBe(false);
469
+
470
+ releaseRevocation();
471
+ await pending;
472
+
473
+ expect(settled).toBe(true);
474
+ });
475
+
476
+ test("throws NotFoundException for a user that does not exist, writing and revoking nothing", async () => {
477
+ /*
478
+ * A missing row must not degrade into a no-op that reports success: an
479
+ * operator acting on a mistyped or stale id would be told two factor auth
480
+ * is now required of somebody, and nobody would be any safer. And nothing
481
+ * may run before the row is confirmed -- an update keyed on an id that
482
+ * does not exist is silent, but a revocation is not, and it would sign out
483
+ * whoever that id belongs to.
484
+ */
485
+ resolveUser(null);
486
+
487
+ await expect(
488
+ UserService.setTwoFactorAuthRequired({
489
+ userId: userId,
490
+ isRequired: true,
491
+ }),
492
+ ).rejects.toThrow(NotFoundException);
493
+
494
+ expect(updateOneByIdSpy).not.toHaveBeenCalled();
495
+ expect(revokeSessionsSpy).not.toHaveBeenCalled();
496
+ });
497
+
498
+ test("logs the change, naming the user it was made against", async () => {
499
+ /*
500
+ * This is one human changing how another human signs in. The user id has
501
+ * to be in the line, or the log records that SOMEBODY's two factor auth
502
+ * requirement changed and an incident review has nothing to go on.
503
+ */
504
+ resolveUser(buildUser({ id: foundUserId, enableTwoFactorAuth: false }));
505
+
506
+ await UserService.setTwoFactorAuthRequired({
507
+ userId: userId,
508
+ isRequired: true,
509
+ });
510
+
511
+ const lines: Array<string> = loggerInfoSpy.mock.calls.map(
512
+ (call: Array<unknown>): string => {
513
+ return String(call[0]);
514
+ },
515
+ );
516
+
517
+ const namingLines: Array<string> = lines.filter(
518
+ (line: string): boolean => {
519
+ return line.includes(foundUserId.toString());
520
+ },
521
+ );
522
+
523
+ expect(namingLines.length).toBeGreaterThan(0);
524
+ });
525
+
526
+ test("REGRESSION: enabling succeeds for a user with ZERO verified factors", async () => {
527
+ /*
528
+ * THE CASE THE WHOLE FEATURE TURNS ON, and the one that used to be
529
+ * impossible.
530
+ *
531
+ * `UserService.onBeforeUpdate` used to refuse any update setting
532
+ * `enableTwoFactorAuth` on an account with no verified authenticator --
533
+ * "Please verify two factor authentication method before you enable two
534
+ * factor authentication." That guard fired for exactly the users an admin
535
+ * mandate exists for: the ones who have never set two factor auth up. An
536
+ * admin could therefore only require it from people who had already
537
+ * volunteered, which is the opposite of what a mandate is.
538
+ *
539
+ * The guard is gone because its premise dissolved: login now sends an
540
+ * account in this state through ENROLMENT rather than refusing it.
541
+ *
542
+ * The update is routed through the real hook here on purpose. Stubbing
543
+ * `updateOneById` alone would make this pass whether the guard exists or
544
+ * not -- the guard lives inside the write path, so the write path is what
545
+ * has to run.
546
+ */
547
+ resolveUser(buildUser({ id: foundUserId, enableTwoFactorAuth: false }));
548
+
549
+ updateOneByIdSpy.mockImplementation(
550
+ async (update: any): Promise<void> => {
551
+ await (
552
+ UserService as unknown as {
553
+ onBeforeUpdate: (updateBy: unknown) => Promise<unknown>;
554
+ }
555
+ ).onBeforeUpdate({
556
+ query: { _id: update.id },
557
+ data: update.data,
558
+ props: update.props,
559
+ limit: 1,
560
+ skip: 0,
561
+ });
562
+ },
563
+ );
564
+
565
+ await expect(
566
+ UserService.setTwoFactorAuthRequired({
567
+ userId: userId,
568
+ isRequired: true,
569
+ }),
570
+ ).resolves.toBeUndefined();
571
+
572
+ expect(updateOneByIdSpy).toHaveBeenCalledTimes(1);
573
+ expect(revokeSessionsSpy).toHaveBeenCalledTimes(1);
574
+ });
575
+
576
+ test("enabling for a user who already has it enabled is idempotent", async () => {
577
+ /*
578
+ * Bulk "require two factor auth" over a selection will always include
579
+ * people who already have it. Short-circuiting on "no change" would look
580
+ * like an optimisation and would quietly drop the session revocation --
581
+ * the half of this method that is doing security work -- for precisely the
582
+ * users an operator was re-asserting the requirement over.
583
+ */
584
+ resolveUser(buildUser({ id: foundUserId, enableTwoFactorAuth: true }));
585
+
586
+ await UserService.setTwoFactorAuthRequired({
587
+ userId: userId,
588
+ isRequired: true,
589
+ });
590
+
591
+ expect(updateOneByIdSpy).toHaveBeenCalledTimes(1);
592
+ expect(updateOneByIdSpy.mock.calls[0][0].data.enableTwoFactorAuth).toBe(
593
+ true,
594
+ );
595
+ expect(revokeSessionsSpy).toHaveBeenCalledTimes(1);
596
+ });
597
+ });
598
+
599
+ describe("resetTwoFactorAuth", () => {
600
+ type DeleteCallForFunction = (spy: any) => any;
601
+
602
+ const deleteCallFor: DeleteCallForFunction = (spy: any): any => {
603
+ return spy.mock.calls[0][0];
604
+ };
605
+
606
+ test("deletes every authenticator app row for the user, as root, unbounded", async () => {
607
+ /*
608
+ * `LIMIT_MAX` rather than a page: a partial delete leaves the user holding
609
+ * an authenticator that still works, so "reset because the phone was
610
+ * stolen" would leave the thief's factor in place. Root because the
611
+ * operator is not the row's owner and has no project-scoped path to it.
612
+ */
613
+ resolveUser(buildUser({ id: foundUserId, enableTwoFactorAuth: true }));
614
+
615
+ await UserService.resetTwoFactorAuth({ userId: userId });
616
+
617
+ expect(totpDeleteBySpy).toHaveBeenCalledTimes(1);
618
+
619
+ const call: any = deleteCallFor(totpDeleteBySpy);
620
+
621
+ expect(call.query).toEqual({ userId: foundUserId });
622
+ expect(call.props).toEqual({ isRoot: true });
623
+ expect(call.limit).toBe(LIMIT_MAX);
624
+ expect(call.skip).toBe(0);
625
+ });
626
+
627
+ test("deletes every security key row for the user, in the same shape", async () => {
628
+ /*
629
+ * Both tables or neither. Clearing only the TOTP rows would report a
630
+ * successful reset while the user's security key still satisfies login --
631
+ * which is fine for a lost phone and useless for a lost laptop, and the
632
+ * operator cannot tell the two apart from the button they pressed.
633
+ */
634
+ resolveUser(buildUser({ id: foundUserId, enableTwoFactorAuth: true }));
635
+
636
+ await UserService.resetTwoFactorAuth({ userId: userId });
637
+
638
+ expect(webAuthnDeleteBySpy).toHaveBeenCalledTimes(1);
639
+
640
+ const call: any = deleteCallFor(webAuthnDeleteBySpy);
641
+
642
+ expect(call.query).toEqual({ userId: foundUserId });
643
+ expect(call.props).toEqual({ isRoot: true });
644
+ expect(call.limit).toBe(LIMIT_MAX);
645
+ expect(call.skip).toBe(0);
646
+ });
647
+
648
+ test("the delete queries carry NO isVerified predicate", async () => {
649
+ /*
650
+ * The tempting-but-wrong filter. `countVerifiedTwoFactorAuthMethods`
651
+ * three methods up DOES filter on `isVerified: true`, so copying that
652
+ * shape down here reads as consistency -- and it is the bug.
653
+ *
654
+ * `UserTotpAuthService.onBeforeCreate` writes `isVerified: false` and only
655
+ * the enrolment step flips it, so every abandoned QR scan leaves a row
656
+ * behind forever. A reset that skipped those would hand the user back the
657
+ * very QR code they could not finish rather than a fresh one, which is
658
+ * exactly the thing they came for help about.
659
+ *
660
+ * Asserted as the exact query object, not "isVerified is not true": a
661
+ * `isVerified: false` predicate would be just as wrong in the other
662
+ * direction, deleting the abandoned rows and keeping the working one.
663
+ */
664
+ resolveUser(buildUser({ id: foundUserId, enableTwoFactorAuth: true }));
665
+
666
+ await UserService.resetTwoFactorAuth({ userId: userId });
667
+
668
+ const violations: Array<string> = [];
669
+
670
+ for (const spy of [totpDeleteBySpy, webAuthnDeleteBySpy]) {
671
+ const query: any = deleteCallFor(spy).query;
672
+ const keys: Array<string> = Object.keys(query);
673
+
674
+ if (keys.length !== 1 || keys[0] !== "userId") {
675
+ violations.push(
676
+ `a reset delete queried on [${keys.join(", ")}] rather than on userId alone`,
677
+ );
678
+ }
679
+ }
680
+
681
+ expect(violations).toEqual([]);
682
+ });
683
+
684
+ test("does NOT touch enableTwoFactorAuth", async () => {
685
+ /*
686
+ * "Reset" means clear the configuration and KEEP the requirement: the
687
+ * account lands on a new QR code at its next sign-in, which is the whole
688
+ * point. Clearing the flag as well would answer "my phone is gone" by
689
+ * silently taking the account off two factor auth -- a downgrade nobody
690
+ * asked for, on a page whose other button is literally "require two factor
691
+ * auth", and an operator helping with a lost phone would have no reason to
692
+ * notice.
693
+ *
694
+ * Asserted as "no user update at all", which is the stronger and simpler
695
+ * fact: this method has no business writing to the User row.
696
+ *
697
+ * BOTH write entry points are checked, not just the one this method
698
+ * happens to use. Asserting only `updateOneById` would leave the claim
699
+ * resting on a method name: a rewrite reaching for `updateBy` directly --
700
+ * the natural shape if somebody ever wanted to clear the flag for a whole
701
+ * selection at once -- would clear `enableTwoFactorAuth` in exactly the
702
+ * way this test exists to forbid and would not be seen at all.
703
+ */
704
+ resolveUser(buildUser({ id: foundUserId, enableTwoFactorAuth: true }));
705
+
706
+ await UserService.resetTwoFactorAuth({ userId: userId });
707
+
708
+ const writeEntryPoints: Array<NamedSpy> = [
709
+ { name: "updateOneById", spy: updateOneByIdSpy },
710
+ { name: "updateBy", spy: updateBySpy },
711
+ ];
712
+
713
+ const violations: Array<string> = [];
714
+
715
+ for (const entryPoint of writeEntryPoints) {
716
+ for (const call of entryPoint.spy.mock.calls) {
717
+ const keys: Array<string> = Object.keys(call[0]?.data || {});
718
+
719
+ violations.push(
720
+ `resetTwoFactorAuth wrote to the User row via ${entryPoint.name} with data keys [${keys.join(", ")}]`,
721
+ );
722
+ }
723
+ }
724
+
725
+ expect(violations).toEqual([]);
726
+ });
727
+
728
+ test("revokes every session, with a reason short enough to store", async () => {
729
+ /*
730
+ * A session that survives a reset is a session that never has to prove the
731
+ * second factor again -- so "reset because the device was stolen" would do
732
+ * nothing at all about the person holding the stolen device, while
733
+ * reporting success.
734
+ *
735
+ * Same ShortText bound as the enable path, for the same reason: an
736
+ * over-long reason fails the write that closes the window.
737
+ */
738
+ resolveUser(buildUser({ id: foundUserId, enableTwoFactorAuth: true }));
739
+
740
+ await UserService.resetTwoFactorAuth({ userId: userId });
741
+
742
+ expect(revokeSessionsSpy).toHaveBeenCalledTimes(1);
743
+
744
+ const revokedUserId: any = revokeSessionsSpy.mock.calls[0][0];
745
+ const reason: string = revokeSessionsSpy.mock.calls[0][1]
746
+ .reason as string;
747
+
748
+ expect(revokedUserId.toString()).toBe(foundUserId.toString());
749
+ expect(typeof reason).toBe("string");
750
+ expect(reason.length).toBeGreaterThan(0);
751
+ expect(reason.length).toBeLessThanOrEqual(ColumnLength.ShortText);
752
+ });
753
+
754
+ test("both deletes complete BEFORE the revocation", async () => {
755
+ /*
756
+ * Ordering, not just presence. Revoking first would open a window between
757
+ * the revocation and the deletes in which the user (or whoever has their
758
+ * device) can sign in again using the authenticator that is about to be
759
+ * removed -- and come out the other side with a live session and no
760
+ * pending enrolment. The window is small and entirely real: the deletes
761
+ * are two round trips.
762
+ */
763
+ resolveUser(buildUser({ id: foundUserId, enableTwoFactorAuth: true }));
764
+
765
+ await UserService.resetTwoFactorAuth({ userId: userId });
766
+
767
+ const totpOrder: number = totpDeleteBySpy.mock
768
+ .invocationCallOrder[0] as number;
769
+ const webAuthnOrder: number = webAuthnDeleteBySpy.mock
770
+ .invocationCallOrder[0] as number;
771
+ const revokeOrder: number = revokeSessionsSpy.mock
772
+ .invocationCallOrder[0] as number;
773
+
774
+ const violations: Array<string> = [];
775
+
776
+ if (!(totpOrder < revokeOrder)) {
777
+ violations.push(
778
+ "sessions were revoked before the authenticator app rows were deleted",
779
+ );
780
+ }
781
+
782
+ if (!(webAuthnOrder < revokeOrder)) {
783
+ violations.push(
784
+ "sessions were revoked before the security key rows were deleted",
785
+ );
786
+ }
787
+
788
+ expect(violations).toEqual([]);
789
+ });
790
+
791
+ test("throws NotFoundException for a user that does not exist, deleting nothing", async () => {
792
+ /*
793
+ * The delete queries are keyed on `userId`, so a missing row would make
794
+ * them harmless no-ops -- which is precisely why the check has to be
795
+ * explicit. Without it an operator on a mistyped id is told the reset
796
+ * worked, walks away, and the person who actually needs it is still
797
+ * locked out.
798
+ */
799
+ resolveUser(null);
800
+
801
+ await expect(
802
+ UserService.resetTwoFactorAuth({ userId: userId }),
803
+ ).rejects.toThrow(NotFoundException);
804
+
805
+ expect(totpDeleteBySpy).not.toHaveBeenCalled();
806
+ expect(webAuthnDeleteBySpy).not.toHaveBeenCalled();
807
+ expect(revokeSessionsSpy).not.toHaveBeenCalled();
808
+ });
809
+
810
+ test("is a successful no-op for a user with no factors at all", async () => {
811
+ /*
812
+ * The operator pressing this button does not know what the user has set
813
+ * up -- that is why they are on the page. Refusing "there is nothing to
814
+ * reset" would turn an idempotent repair into an error the operator has to
815
+ * interpret, and it is also the state a half-finished previous reset
816
+ * leaves behind, where pressing it again is the obvious thing to do.
817
+ */
818
+ resolveUser(buildUser({ id: foundUserId, enableTwoFactorAuth: true }));
819
+
820
+ totpDeleteBySpy.mockImplementation(async (): Promise<number> => {
821
+ return 0;
822
+ });
823
+
824
+ webAuthnDeleteBySpy.mockImplementation(async (): Promise<number> => {
825
+ return 0;
826
+ });
827
+
828
+ await expect(
829
+ UserService.resetTwoFactorAuth({ userId: userId }),
830
+ ).resolves.toBeUndefined();
831
+
832
+ expect(totpDeleteBySpy).toHaveBeenCalledTimes(1);
833
+ expect(webAuthnDeleteBySpy).toHaveBeenCalledTimes(1);
834
+ expect(revokeSessionsSpy).toHaveBeenCalledTimes(1);
835
+ });
836
+
837
+ test("resets an Enabled - Pending Setup account with one abandoned enrolment", async () => {
838
+ /*
839
+ * The state an admin mandate creates, plus one QR code the user started
840
+ * and never finished. Nothing is verified, so any implementation that
841
+ * reasoned about verified factors -- a guard refusing to remove the "last"
842
+ * one, or a filtered delete -- behaves differently here than in the
843
+ * ordinary case, and this is the account least able to absorb a mistake:
844
+ * it is already unable to sign in without completing enrolment.
845
+ */
846
+ resolveUser(buildUser({ id: foundUserId, enableTwoFactorAuth: true }));
847
+
848
+ totpDeleteBySpy.mockImplementation(async (): Promise<number> => {
849
+ // One unverified row cleared.
850
+ return 1;
851
+ });
852
+
853
+ await expect(
854
+ UserService.resetTwoFactorAuth({ userId: userId }),
855
+ ).resolves.toBeUndefined();
856
+
857
+ expect(deleteCallFor(totpDeleteBySpy).query).toEqual({
858
+ userId: foundUserId,
859
+ });
860
+ expect(updateOneByIdSpy).not.toHaveBeenCalled();
861
+ expect(revokeSessionsSpy).toHaveBeenCalledTimes(1);
862
+ });
863
+ });
864
+
865
+ describe("the removed hooks", () => {
866
+ type CallOnBeforeUpdateFunction = (updateBy: unknown) => Promise<any>;
867
+
868
+ /*
869
+ * `onBeforeUpdate` is protected, so it is reached through a cast. Driving
870
+ * the hook directly is the point: it is where the deleted guard lived, and
871
+ * a test that went through `updateOneById` would be testing a stub.
872
+ */
873
+ const callOnBeforeUpdate: CallOnBeforeUpdateFunction = async (
874
+ updateBy: unknown,
875
+ ): Promise<any> => {
876
+ return await (
877
+ UserService as unknown as {
878
+ onBeforeUpdate: (u: unknown) => Promise<unknown>;
879
+ }
880
+ ).onBeforeUpdate(updateBy);
881
+ };
882
+
883
+ /*
884
+ * ---------------------------------------------------------------------
885
+ * The guard that REPLACED the deleted one, and the reason it had to
886
+ * exist at all.
887
+ *
888
+ * `enableTwoFactorAuth` carries `update: [Permission.CurrentUser]`
889
+ * (Common/Models/DatabaseModels/User.ts) and the User table's row ACL
890
+ * scopes updates to the caller's own id, so every signed-in user can
891
+ * write their own copy of this flag through the ordinary CRUD API -- and
892
+ * the product ships the button that does it, at Dashboard > Profile >
893
+ * Two Factor Authentication.
894
+ *
895
+ * Without the guard the whole feature is self-undoing: the admin requires
896
+ * two factor auth, the user is marched through enrolment at their next
897
+ * sign-in, and then from the session they just earned they flip the
898
+ * toggle back off and delete the factor -- which the removal of the
899
+ * onBeforeDelete guards now also permits. The account is password-only
900
+ * again and the Authentication page reports the mandate as simply absent.
901
+ * ---------------------------------------------------------------------
902
+ */
903
+ test("onBeforeUpdate refuses to let a non-root caller turn the requirement OFF", async () => {
904
+ await expect(
905
+ callOnBeforeUpdate({
906
+ query: { _id: userId },
907
+ data: { enableTwoFactorAuth: false },
908
+ props: {},
909
+ }),
910
+ ).rejects.toThrow(BadDataException);
911
+ });
912
+
913
+ test("the refusal names an administrator, so the user knows who to ask", async () => {
914
+ /*
915
+ * The message is the only thing the user sees when the profile toggle
916
+ * refuses. "Bad data" would send them to support with nothing to say.
917
+ */
918
+ await expect(
919
+ callOnBeforeUpdate({
920
+ query: { _id: userId },
921
+ data: { enableTwoFactorAuth: false },
922
+ props: {},
923
+ }),
924
+ ).rejects.toThrow(/administrator/i);
925
+ });
926
+
927
+ test("a ROOT caller may turn the requirement off -- that is the admin endpoint", async () => {
928
+ /*
929
+ * setTwoFactorAuthRequired({ isRequired: false }) writes with
930
+ * props.isRoot, so the guard must not catch it. If it did, the admin's
931
+ * "Do Not Require" button would fail for every user.
932
+ */
933
+ await expect(
934
+ callOnBeforeUpdate({
935
+ query: { _id: userId },
936
+ data: { enableTwoFactorAuth: false },
937
+ props: { isRoot: true },
938
+ }),
939
+ ).resolves.toBeDefined();
940
+ });
941
+
942
+ test("a non-root caller may still turn the requirement ON for themselves", async () => {
943
+ /*
944
+ * Self-service enrolment stays. The guard is deliberately one-way: it
945
+ * bounds who can WEAKEN an account, not who can strengthen one.
946
+ */
947
+ await expect(
948
+ callOnBeforeUpdate({
949
+ query: { _id: userId },
950
+ data: { enableTwoFactorAuth: true },
951
+ props: {},
952
+ }),
953
+ ).resolves.toBeDefined();
954
+ });
955
+
956
+ test("an unrelated non-root update is untouched by the guard", async () => {
957
+ /*
958
+ * The guard keys on the value being exactly `false`, so an update that
959
+ * does not mention the column at all must sail through -- otherwise
960
+ * every profile edit in the product starts failing.
961
+ */
962
+ await expect(
963
+ callOnBeforeUpdate({
964
+ query: { _id: userId },
965
+ data: { name: "Someone" },
966
+ props: {},
967
+ }),
968
+ ).resolves.toBeDefined();
969
+ });
970
+
971
+ test("onBeforeUpdate allows enableTwoFactorAuth: true for a user with no verified factor", async () => {
972
+ /*
973
+ * The deleted guard, asserted at its own level.
974
+ *
975
+ * It threw "Please verify two factor authentication method before you
976
+ * enable two factor authentication." for exactly this update, which made
977
+ * an admin mandate impossible for the users it was meant for -- and would
978
+ * ALSO have made the Login enrolment flow's own write fail, since that
979
+ * write happens before any factor is verified.
980
+ */
981
+ await expect(
982
+ callOnBeforeUpdate({
983
+ query: { _id: userId },
984
+ data: { enableTwoFactorAuth: true },
985
+ props: { isRoot: true },
986
+ limit: 1,
987
+ skip: 0,
988
+ }),
989
+ ).resolves.toBeDefined();
990
+ });
991
+
992
+ test("that same update issues no probes into the authenticator tables", async () => {
993
+ /*
994
+ * The guard's fingerprint, and a second line of defence behind the test
995
+ * above. A resurrected guard has to ask one of these two services how
996
+ * many factors the user has before it can decide to throw, so "no probe
997
+ * was made" catches a reinstated check even in a form that happens to
998
+ * let this particular update through.
999
+ *
1000
+ * Note the spy list below covers `countBy` and `findBy`, NOT `findOneBy`
1001
+ * -- and the original guard probed with `findOneBy`, which
1002
+ * DatabaseService routes through a private `_findBy` rather than the
1003
+ * public method spied here. So this assertion alone would NOT have
1004
+ * caught the guard in its original form. What catches that is the
1005
+ * non-empty `findBy` stub in beforeEach, which lets the restored guard
1006
+ * actually reach its throw.
1007
+ *
1008
+ * It is also a cost assertion in its own right: `onBeforeUpdate` runs on
1009
+ * EVERY user update in the product -- lastActive being written on every
1010
+ * authenticated request included -- so two extra COUNT queries here would
1011
+ * be two extra queries on one of the hottest paths there is.
1012
+ */
1013
+ await callOnBeforeUpdate({
1014
+ query: { _id: userId },
1015
+ data: { enableTwoFactorAuth: true },
1016
+ props: { isRoot: true },
1017
+ limit: 1,
1018
+ skip: 0,
1019
+ });
1020
+
1021
+ const violations: Array<string> = [];
1022
+
1023
+ for (const spy of authenticatorProbeSpies()) {
1024
+ if (spy.mock.calls.length > 0) {
1025
+ violations.push(
1026
+ `onBeforeUpdate made ${spy.mock.calls.length} call(s) into an authenticator service`,
1027
+ );
1028
+ }
1029
+ }
1030
+
1031
+ expect(violations).toEqual([]);
1032
+ expect(findBySpy).not.toHaveBeenCalled();
1033
+ });
1034
+
1035
+ test("onBeforeUpdate still carries the affected users forward for a password update", async () => {
1036
+ /*
1037
+ * REGRESSION GUARD FOR THE DELETION ITSELF. The removed block sat directly
1038
+ * beside this one, and this one is load-bearing in a way that is entirely
1039
+ * invisible from `onBeforeUpdate`: `onUpdateSuccess` iterates
1040
+ * `carryForward` to revoke sessions and send the "Password Changed." mail.
1041
+ * An empty carryForward is not an error -- the loop simply runs zero
1042
+ * times, so a password change would silently stop signing out the stolen
1043
+ * session it exists to kill, and stop telling the user anything happened.
1044
+ *
1045
+ * The SELECT is pinned as well as the rows, and that half is not
1046
+ * decoration. `onUpdateSuccess` addresses the mail with `user.email!` --
1047
+ * a non-null assertion over a column it did not fetch itself. Narrowing
1048
+ * this select to `_id` alone still produces a carryForward of the right
1049
+ * length with the right ids, so every assertion about the rows keeps
1050
+ * passing while every "Password Changed." mail goes out to `undefined`.
1051
+ * Because `findBy` is stubbed here, the returned row's own `email` proves
1052
+ * nothing about that; only the select does.
1053
+ */
1054
+ const affected: User = buildUser({
1055
+ id: foundUserId,
1056
+ email: "user@example.com",
1057
+ });
1058
+
1059
+ findBySpy.mockImplementation(async (): Promise<Array<User>> => {
1060
+ return [affected];
1061
+ });
1062
+
1063
+ const result: any = await callOnBeforeUpdate({
1064
+ query: { _id: userId },
1065
+ data: { password: new HashedString("a-perfectly-fine-password") },
1066
+ props: { isRoot: true },
1067
+ limit: 1,
1068
+ skip: 0,
1069
+ });
1070
+
1071
+ expect(findBySpy).toHaveBeenCalledTimes(1);
1072
+ expect(result.carryForward).toHaveLength(1);
1073
+ expect(result.carryForward[0].id.toString()).toBe(foundUserId.toString());
1074
+
1075
+ const select: any = findBySpy.mock.calls[0][0].select;
1076
+ const violations: Array<string> = [];
1077
+
1078
+ for (const column of ["_id", "email"]) {
1079
+ if (select[column] !== true) {
1080
+ violations.push(
1081
+ `onBeforeUpdate did not carry ${column} forward for a password update; onUpdateSuccess needs it`,
1082
+ );
1083
+ }
1084
+ }
1085
+
1086
+ expect(violations).toEqual([]);
1087
+ });
1088
+
1089
+ test("neither authenticator service overrides onBeforeDelete any more", async () => {
1090
+ /*
1091
+ * Both services used to refuse the deletion of a user's LAST verified
1092
+ * factor while `enableTwoFactorAuth` was on, advising them to "disable two
1093
+ * factor auth before deleting this item".
1094
+ *
1095
+ * Both halves of that stopped being true. The advice is impossible to
1096
+ * follow for a user whose two factor auth was MANDATED -- they cannot turn
1097
+ * the requirement off -- so the guard did not protect them, it stranded
1098
+ * them on an authenticator they may have been trying to replace, and it
1099
+ * would have made `resetTwoFactorAuth` throw on the one account that needs
1100
+ * it most. And the lockout it protected against no longer exists: an
1101
+ * account required to use two factor auth with nothing set up is now sent
1102
+ * through enrolment at its next sign-in, so deleting the last factor costs
1103
+ * a QR code, not an account.
1104
+ *
1105
+ * (It was also wrong on its own terms -- it counted the factors that
1106
+ * existed BEFORE the delete, once per item, inside a loop over the whole
1107
+ * delete set, so removing two verified factors in one call passed the
1108
+ * check twice and landed in the state it existed to prevent.)
1109
+ *
1110
+ * Identity against `DatabaseService.prototype` is the robust form: it does
1111
+ * not care what a reinstated guard would decide, only that the two
1112
+ * services still inherit the placeholder.
1113
+ */
1114
+ const violations: Array<string> = [];
1115
+
1116
+ const inherited: unknown = (DatabaseService.prototype as any)
1117
+ .onBeforeDelete;
1118
+
1119
+ if ((UserTotpAuthService as any).onBeforeDelete !== inherited) {
1120
+ violations.push("UserTotpAuthService overrides onBeforeDelete again");
1121
+ }
1122
+
1123
+ if ((UserWebAuthnService as any).onBeforeDelete !== inherited) {
1124
+ violations.push("UserWebAuthnService overrides onBeforeDelete again");
1125
+ }
1126
+
1127
+ expect(violations).toEqual([]);
1128
+ });
1129
+
1130
+ test("the override comparison can actually detect an override", async () => {
1131
+ /*
1132
+ * Pairs with the test above so it cannot pass vacuously. If
1133
+ * `DatabaseService.prototype.onBeforeDelete` were ever undefined, or the
1134
+ * decorator started producing a fresh function per access, the identity
1135
+ * check would compare two nothings and report success no matter what the
1136
+ * two services did.
1137
+ *
1138
+ * `UserService` genuinely DOES override `onBeforeDelete` (it refuses to
1139
+ * delete a user who is still a member of a project), so it is the control:
1140
+ * the comparison must come out different for it.
1141
+ */
1142
+ const inherited: unknown = (DatabaseService.prototype as any)
1143
+ .onBeforeDelete;
1144
+
1145
+ expect(typeof inherited).toBe("function");
1146
+ expect((UserService as any).onBeforeDelete).not.toBe(inherited);
1147
+ });
1148
+
1149
+ test("deleting the last verified factor of a mandated account no longer throws", async () => {
1150
+ /*
1151
+ * The removal expressed as behaviour rather than as identity, because
1152
+ * behaviour is what the product depends on. This is precisely the call
1153
+ * `resetTwoFactorAuth` makes for a user whose two factor auth is REQUIRED
1154
+ * and whose only factor is verified -- the lost-phone case -- and the old
1155
+ * guard turned it into "Please disable two factor auth before deleting
1156
+ * this item", advice that user could not act on.
1157
+ */
1158
+ const deleteBy: unknown = {
1159
+ query: { userId: userId },
1160
+ limit: LIMIT_MAX,
1161
+ skip: 0,
1162
+ props: { isRoot: true },
1163
+ };
1164
+
1165
+ for (const service of [UserTotpAuthService, UserWebAuthnService]) {
1166
+ await expect(
1167
+ (
1168
+ service as unknown as {
1169
+ onBeforeDelete: (d: unknown) => Promise<unknown>;
1170
+ }
1171
+ ).onBeforeDelete(deleteBy),
1172
+ ).resolves.toBeDefined();
1173
+ }
1174
+
1175
+ // ...and it reached that answer without consulting anything.
1176
+ const violations: Array<string> = [];
1177
+
1178
+ for (const spy of authenticatorProbeSpies()) {
1179
+ if (spy.mock.calls.length > 0) {
1180
+ violations.push(
1181
+ "onBeforeDelete probed an authenticator service before allowing the delete",
1182
+ );
1183
+ }
1184
+ }
1185
+
1186
+ expect(violations).toEqual([]);
1187
+ expect(findOneBySpy).not.toHaveBeenCalled();
1188
+ });
1189
+ });
1190
+ });