@oneuptime/common 12.0.27 → 12.0.29

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 (204) hide show
  1. package/Models/AnalyticsModels/RumSessionChunk.ts +52 -0
  2. package/Models/DatabaseModels/CloudResource.ts +17 -1
  3. package/Models/DatabaseModels/DashboardDomain.ts +43 -0
  4. package/Models/DatabaseModels/MonitorTemplate.ts +15 -3
  5. package/Models/DatabaseModels/RumApplication.ts +35 -2
  6. package/Models/DatabaseModels/StatusPageDomain.ts +43 -0
  7. package/Server/API/DashboardDomainAPI.ts +74 -0
  8. package/Server/API/MicrosoftTeamsAPI.ts +81 -180
  9. package/Server/API/StatusPageDomainAPI.ts +74 -0
  10. package/Server/API/TelemetryAPI.ts +104 -15
  11. package/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.ts +43 -0
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.ts +40 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  14. package/Server/Middleware/ProjectAuthorization.ts +6 -1
  15. package/Server/Services/DashboardDomainService.ts +131 -0
  16. package/Server/Services/LogPipelineProcessorService.ts +88 -0
  17. package/Server/Services/MonitorTemplateService.ts +70 -11
  18. package/Server/Services/StatusPageDomainService.ts +163 -3
  19. package/Server/Services/StatusPagePrivateUserSessionService.ts +197 -56
  20. package/Server/Types/Domain.ts +17 -0
  21. package/Server/Utils/APIKey/AccessPermission.ts +24 -0
  22. package/Server/Utils/Greenlock/Greenlock.ts +156 -58
  23. package/Server/Utils/LogPipelineProcessorValidation.ts +112 -0
  24. package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.ts +1094 -45
  25. package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.ts +153 -17
  26. package/Server/Utils/SessionReplay/SessionReplayGateCache.ts +12 -5
  27. package/Server/Utils/SessionReplay/SessionReplayIdentity.ts +102 -0
  28. package/Server/Utils/SessionReplay/SessionReplayReadService.ts +11 -2
  29. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.ts +23 -11
  30. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.ts +0 -170
  31. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.ts +158 -0
  32. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +23 -11
  33. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +35 -13
  34. package/Tests/App/Dashboard/MonitorTemplateColumnAndFacet.test.tsx +505 -0
  35. package/Tests/App/StatusPage/StatusPageLoginCodeBootstrap.test.ts +106 -0
  36. package/Tests/App/StatusPage/StatusPageLoginCodeUtil.test.ts +84 -0
  37. package/Tests/App/StatusPage/StatusPageRedirectSafety.test.ts +119 -0
  38. package/Tests/Models/DatabaseModels/SessionReplayModels.test.ts +81 -0
  39. package/Tests/Models/MonitorTemplateMonitorNameColumn.test.ts +258 -0
  40. package/Tests/Server/API/CustomDomainReissueSslAPI.test.ts +469 -0
  41. package/Tests/Server/API/MicrosoftTeamsBotMessagesEndpoint.test.ts +729 -0
  42. package/Tests/Server/API/MicrosoftTeamsBotTestEndpoint.test.ts +26 -0
  43. package/Tests/Server/API/MicrosoftTeamsLegacyWebhookRemoval.test.ts +402 -0
  44. package/Tests/Server/API/SessionReplayAPI.test.ts +232 -0
  45. package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +4 -0
  46. package/Tests/Server/Services/AddCertificateReissueRequestedAtToDomainsMigration.test.ts +287 -0
  47. package/Tests/Server/Services/CustomDomainCertificateReissue.test.ts +477 -0
  48. package/Tests/Server/Services/LogPipelineProcessorSaveValidation.test.ts +249 -0
  49. package/Tests/Server/Services/MonitorTemplateServiceBulkSync.test.ts +549 -0
  50. package/Tests/Server/Services/MonitorTemplateServiceNetworkDeviceSync.test.ts +16 -3
  51. package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +57 -0
  52. package/Tests/Server/Services/SecurityEventLastErrorColumnWidth.test.ts +151 -15
  53. package/Tests/Server/Services/StatusPagePrivateUserSessionService.test.ts +587 -0
  54. package/Tests/Server/Types/Database/Permissions/ApiKeyFileAccess.test.ts +286 -0
  55. package/Tests/Server/Types/Database/Permissions/MonitorTemplateColumnAccess.test.ts +328 -0
  56. package/Tests/Server/Types/Database/QueryHelperFindWithSameTextAndIsNull.test.ts +83 -0
  57. package/Tests/Server/Utils/AI/SRE/ConfidenceSignal.test.ts +377 -0
  58. package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +16 -1
  59. package/Tests/Server/Utils/Greenlock/CertificateRenewalSchedule.test.ts +250 -0
  60. package/Tests/Server/Utils/LogPipelineProcessorValidation.test.ts +198 -0
  61. package/Tests/Server/Utils/SecurityEvent/ConnectorErrorMessage.test.ts +48 -9
  62. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsAuth.test.ts +726 -0
  63. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsClient.test.ts +74 -11
  64. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsErrorHandling.test.ts +1107 -0
  65. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPoller.test.ts +20 -3
  66. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerFailureTaxonomy.test.ts +51 -11
  67. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerHardening.test.ts +1246 -0
  68. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsRequestContract.test.ts +961 -0
  69. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsResponseParsing.test.ts +653 -0
  70. package/Tests/Server/Utils/Workspace/MicrosoftTeamsActionAuthorization.test.ts +880 -0
  71. package/Tests/Types/API/HTTPResponse.test.ts +175 -0
  72. package/Tests/Types/API/Route.test.ts +36 -4
  73. package/Tests/Types/Database/DatabaseProperty.test.ts +171 -0
  74. package/Tests/Types/DateLocalShortDateTimeString.test.ts +518 -0
  75. package/Tests/Types/DateLocalTimeString.test.ts +259 -0
  76. package/Tests/Types/DateUserPrefers12HourFormat.test.ts +316 -0
  77. package/Tests/UI/Components/LogsHistogram.test.tsx +41 -2
  78. package/Tests/UI/Components/LogsViewerEmptyStateTimeRange.test.tsx +212 -0
  79. package/Tests/UI/Components/TelemetryChartClockFormat.test.tsx +359 -0
  80. package/Tests/UI/Components/TimeRangePickerClockFormat.test.tsx +422 -0
  81. package/Tests/UI/Components/TimeRangePickerDropdown.test.tsx +146 -8
  82. package/Tests/UI/MicrosoftTeams/MicrosoftTeamsMessagingEndpointGuidance.test.tsx +520 -0
  83. package/Tests/UI/Utils/Navigation.test.ts +82 -0
  84. package/Tests/Utils/Array.test.ts +100 -0
  85. package/Tests/Utils/CertificateReissue.test.ts +225 -0
  86. package/Tests/Utils/Grok/Grok.test.ts +492 -0
  87. package/Tests/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.test.ts +181 -13
  88. package/Types/API/Route.ts +20 -5
  89. package/Types/Date.ts +143 -13
  90. package/Types/Rum/SessionReplay.ts +76 -1
  91. package/UI/Components/Date/TimeRangePickerDropdown.tsx +9 -5
  92. package/UI/Components/LogsViewer/LogsViewer.tsx +12 -7
  93. package/UI/Components/LogsViewer/components/HistogramTooltip.tsx +18 -17
  94. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +21 -28
  95. package/UI/Components/LogsViewer/components/LogsHistogram.tsx +2 -4
  96. package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +2 -4
  97. package/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.tsx +17 -16
  98. package/UI/Utils/Navigation.ts +42 -0
  99. package/Utils/Array.ts +37 -0
  100. package/Utils/CertificateReissue.ts +128 -0
  101. package/Utils/Grok/Grok.ts +461 -0
  102. package/Utils/Grok/GrokPatterns.ts +118 -0
  103. package/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.ts +58 -6
  104. package/build/dist/Models/AnalyticsModels/RumSessionChunk.js +48 -0
  105. package/build/dist/Models/AnalyticsModels/RumSessionChunk.js.map +1 -1
  106. package/build/dist/Models/DatabaseModels/CloudResource.js +19 -2
  107. package/build/dist/Models/DatabaseModels/CloudResource.js.map +1 -1
  108. package/build/dist/Models/DatabaseModels/DashboardDomain.js +44 -0
  109. package/build/dist/Models/DatabaseModels/DashboardDomain.js.map +1 -1
  110. package/build/dist/Models/DatabaseModels/MonitorTemplate.js +17 -4
  111. package/build/dist/Models/DatabaseModels/MonitorTemplate.js.map +1 -1
  112. package/build/dist/Models/DatabaseModels/RumApplication.js +37 -3
  113. package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -1
  114. package/build/dist/Models/DatabaseModels/StatusPageDomain.js +44 -0
  115. package/build/dist/Models/DatabaseModels/StatusPageDomain.js.map +1 -1
  116. package/build/dist/Server/API/DashboardDomainAPI.js +43 -1
  117. package/build/dist/Server/API/DashboardDomainAPI.js.map +1 -1
  118. package/build/dist/Server/API/MicrosoftTeamsAPI.js +74 -118
  119. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  120. package/build/dist/Server/API/StatusPageDomainAPI.js +43 -1
  121. package/build/dist/Server/API/StatusPageDomainAPI.js.map +1 -1
  122. package/build/dist/Server/API/TelemetryAPI.js +67 -14
  123. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  124. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.js +34 -0
  125. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.js.map +1 -0
  126. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.js +28 -0
  127. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.js.map +1 -0
  128. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  129. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  130. package/build/dist/Server/Middleware/ProjectAuthorization.js +6 -1
  131. package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
  132. package/build/dist/Server/Services/DashboardDomainService.js +104 -0
  133. package/build/dist/Server/Services/DashboardDomainService.js.map +1 -1
  134. package/build/dist/Server/Services/LogPipelineProcessorService.js +65 -0
  135. package/build/dist/Server/Services/LogPipelineProcessorService.js.map +1 -1
  136. package/build/dist/Server/Services/MonitorTemplateService.js +57 -11
  137. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  138. package/build/dist/Server/Services/StatusPageDomainService.js +132 -3
  139. package/build/dist/Server/Services/StatusPageDomainService.js.map +1 -1
  140. package/build/dist/Server/Services/StatusPagePrivateUserSessionService.js +112 -32
  141. package/build/dist/Server/Services/StatusPagePrivateUserSessionService.js.map +1 -1
  142. package/build/dist/Server/Types/Domain.js +16 -0
  143. package/build/dist/Server/Types/Domain.js.map +1 -1
  144. package/build/dist/Server/Utils/APIKey/AccessPermission.js +24 -0
  145. package/build/dist/Server/Utils/APIKey/AccessPermission.js.map +1 -1
  146. package/build/dist/Server/Utils/Greenlock/Greenlock.js +110 -35
  147. package/build/dist/Server/Utils/Greenlock/Greenlock.js.map +1 -1
  148. package/build/dist/Server/Utils/LogPipelineProcessorValidation.js +77 -0
  149. package/build/dist/Server/Utils/LogPipelineProcessorValidation.js.map +1 -0
  150. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js +777 -36
  151. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js.map +1 -1
  152. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js +108 -17
  153. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js.map +1 -1
  154. package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js.map +1 -1
  155. package/build/dist/Server/Utils/SessionReplay/SessionReplayIdentity.js +59 -0
  156. package/build/dist/Server/Utils/SessionReplay/SessionReplayIdentity.js.map +1 -0
  157. package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js +9 -2
  158. package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js.map +1 -1
  159. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js +17 -6
  160. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js.map +1 -1
  161. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js +0 -159
  162. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js.map +1 -1
  163. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.js +128 -0
  164. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.js.map +1 -0
  165. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +17 -6
  166. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
  167. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +25 -13
  168. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  169. package/build/dist/Types/API/Route.js +16 -5
  170. package/build/dist/Types/API/Route.js.map +1 -1
  171. package/build/dist/Types/Date.js +115 -13
  172. package/build/dist/Types/Date.js.map +1 -1
  173. package/build/dist/Types/Rum/SessionReplay.js +38 -0
  174. package/build/dist/Types/Rum/SessionReplay.js.map +1 -1
  175. package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js +9 -5
  176. package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js.map +1 -1
  177. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +12 -7
  178. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  179. package/build/dist/UI/Components/LogsViewer/components/HistogramTooltip.js +15 -14
  180. package/build/dist/UI/Components/LogsViewer/components/HistogramTooltip.js.map +1 -1
  181. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +17 -22
  182. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  183. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +2 -4
  184. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
  185. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +2 -4
  186. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
  187. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js +15 -14
  188. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js.map +1 -1
  189. package/build/dist/UI/Utils/Navigation.js +30 -0
  190. package/build/dist/UI/Utils/Navigation.js.map +1 -1
  191. package/build/dist/Utils/Array.js +25 -0
  192. package/build/dist/Utils/Array.js.map +1 -1
  193. package/build/dist/Utils/CertificateReissue.js +97 -0
  194. package/build/dist/Utils/CertificateReissue.js.map +1 -0
  195. package/build/dist/Utils/Grok/Grok.js +324 -0
  196. package/build/dist/Utils/Grok/Grok.js.map +1 -0
  197. package/build/dist/Utils/Grok/GrokPatterns.js +112 -0
  198. package/build/dist/Utils/Grok/GrokPatterns.js.map +1 -0
  199. package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js +63 -3
  200. package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js.map +1 -1
  201. package/package.json +1 -1
  202. package/UI/Utils/JsonWebToken.ts +0 -14
  203. package/build/dist/UI/Utils/JsonWebToken.js +0 -10
  204. package/build/dist/UI/Utils/JsonWebToken.js.map +0 -1
@@ -450,6 +450,56 @@ export default class RumSessionChunk extends AnalyticsBaseModel {
450
450
  });
451
451
  });
452
452
 
453
+ /*
454
+ * WHERE the page was while this chunk was open.
455
+ *
456
+ * The chunk table used to carry routeCount but not the routes, so the
457
+ * session header's entryUrl / exitUrl / routes[] could only ever be
458
+ * whatever chunk 0 happened to know - which for a single-page app is the
459
+ * landing page, forever. pageCount said "7 pages" on the same row where
460
+ * routes[] held one element, and the "Exit page URL (exact)" filter
461
+ * could not match a page the user demonstrably reached.
462
+ *
463
+ * Both are already scrubbed on the client AND re-scrubbed at ingest:
464
+ * these render under the wider session-metadata ACL, so an unscrubbed
465
+ * `/reset-password?token=...` here reaches more readers than the payload
466
+ * itself does.
467
+ */
468
+ const urlColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
469
+ key: "url",
470
+ title: "URL",
471
+ description:
472
+ "Scrubbed URL the page was on when this chunk was flushed. The session's exit URL is the latest of these.",
473
+ /*
474
+ * Required with an empty default rather than nullable. Chunks written
475
+ * before this column existed read back as "", which is what the
476
+ * finalizer's argMinIf/argMaxIf skip over so a pre-migration session
477
+ * falls back to its provisional header instead of losing its URLs.
478
+ */
479
+ required: true,
480
+ defaultValue: "",
481
+ type: TableColumnType.Text,
482
+ codec: [{ codec: "ZSTD", level: 1 }],
483
+ accessControl: chunkAccessControl,
484
+ });
485
+
486
+ const routesColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
487
+ key: "routes",
488
+ title: "Routes",
489
+ description:
490
+ "Distinct scrubbed URLs visited while this chunk was open, in order. The session's routes[] is the union of these.",
491
+ /*
492
+ * Required, like RumSession.routes. ClickHouse refuses
493
+ * Nullable(Array(String)) outright, so an array column can never be
494
+ * optional - it is empty or it is absent.
495
+ */
496
+ required: true,
497
+ defaultValue: [],
498
+ type: TableColumnType.ArrayText,
499
+ codec: [{ codec: "ZSTD", level: 1 }],
500
+ accessControl: chunkAccessControl,
501
+ });
502
+
453
503
  const retentionDateColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
454
504
  key: "retentionDate",
455
505
  title: "Retention Date",
@@ -510,6 +560,8 @@ export default class RumSessionChunk extends AnalyticsBaseModel {
510
560
  payloadColumn,
511
561
  payloadBytesColumn,
512
562
  ...counterColumns,
563
+ urlColumn,
564
+ routesColumn,
513
565
  retentionDateColumn,
514
566
  ],
515
567
  sortKeys: ["projectId", "sessionId", "tabId", "chunkIndex"],
@@ -283,8 +283,22 @@ export default class CloudResource extends BaseModel {
283
283
  })
284
284
  public description?: string = undefined;
285
285
 
286
+ /*
287
+ * Creatable, never updatable - see the identical note on
288
+ * RumApplication.appIdentifier. `create: []` on a required column that the
289
+ * Cloud Resources create form declares made manual creation impossible:
290
+ * the field was stripped from the form and the server then rejected the
291
+ * POST for the very value it had just removed.
292
+ */
286
293
  @ColumnAccessControl({
287
- create: [],
294
+ create: [
295
+ Permission.ProjectOwner,
296
+ Permission.ProjectAdmin,
297
+ Permission.ProjectMember,
298
+ Permission.SettingsAdmin,
299
+ Permission.SettingsMember,
300
+ Permission.CreateCloudResource,
301
+ ],
288
302
  read: [
289
303
  Permission.ProjectOwner,
290
304
  Permission.ProjectAdmin,
@@ -305,6 +319,8 @@ export default class CloudResource extends BaseModel {
305
319
  description:
306
320
  "Stable identifier for this managed-compute workload (service.name, falling back to host.name). Identity key for this resource.",
307
321
  })
322
+ /* Case-insensitive uniqueness; see RumApplication.appIdentifier. */
323
+ @UniqueColumnBy("projectId")
308
324
  @Column({
309
325
  nullable: false,
310
326
  type: ColumnType.ShortText,
@@ -754,4 +754,47 @@ export default class DashboardDomain extends BaseModel {
754
754
  default: false,
755
755
  })
756
756
  public isCustomCertificate?: boolean = undefined;
757
+
758
+ /*
759
+ * When a reissue of this domain's Let's Encrypt certificate was last
760
+ * REQUESTED from the dashboard - not when one last succeeded.
761
+ *
762
+ * This is the whole state behind the reissue cooldown. Certificates come
763
+ * from a Let's Encrypt account shared by every customer on the
764
+ * installation, and a failed order still spends a validation attempt
765
+ * against that account, so the stamp is written before the order is
766
+ * attempted and is left in place whether the order succeeds or fails.
767
+ *
768
+ * Null means this domain has never had a reissue requested; automated
769
+ * renewal never writes here, so the cron keeping a certificate fresh can
770
+ * never be the reason a customer's button is refused.
771
+ */
772
+ @ColumnAccessControl({
773
+ create: [],
774
+ read: [
775
+ Permission.ProjectOwner,
776
+ Permission.ProjectAdmin,
777
+ Permission.ProjectMember,
778
+ Permission.Viewer,
779
+ Permission.SettingsAdmin,
780
+ Permission.SettingsMember,
781
+ Permission.SettingsViewer,
782
+ Permission.ReadDashboardDomain,
783
+ ],
784
+ update: [],
785
+ })
786
+ @TableColumn({
787
+ required: false,
788
+ computed: true,
789
+ type: TableColumnType.Date,
790
+ title: "Certificate Reissue Requested At",
791
+ description:
792
+ "When a reissue of the SSL certificate for this domain was last requested. Used to rate limit how often a certificate can be reissued.",
793
+ })
794
+ @Column({
795
+ type: ColumnType.Date,
796
+ nullable: true,
797
+ unique: false,
798
+ })
799
+ public certificateReissueRequestedAt?: Date = undefined;
757
800
  }
@@ -313,15 +313,27 @@ export default class MonitorTemplate extends BaseModel {
313
313
  Permission.EditMonitorTemplate,
314
314
  ],
315
315
  })
316
+ /*
317
+ * OPTIONAL, and the reason it is optional is the whole of issue #3486.
318
+ *
319
+ * A Network Device auto-import rule names every monitor it provisions
320
+ * "<device> - <this>". While this column was required, an operator who only
321
+ * wanted the device's own name had nothing to type that would not become a
322
+ * suffix on every imported router in the estate. Left blank, the monitor is
323
+ * named after the resource alone.
324
+ *
325
+ * Nullable rather than defaulted: a default would put the same invented
326
+ * suffix back on every template that never asked for one.
327
+ */
316
328
  @TableColumn({
317
- required: true,
329
+ required: false,
318
330
  type: TableColumnType.ShortText,
319
331
  title: "Monitor Name",
320
332
  description:
321
- "Default name applied to monitors created from this template. Users can override on creation.",
333
+ "Default name applied to monitors created from this template. Users can override on creation. Leave it blank to name each monitor after the resource it watches.",
322
334
  })
323
335
  @Column({
324
- nullable: false,
336
+ nullable: true,
325
337
  type: ColumnType.ShortText,
326
338
  length: ColumnLength.ShortText,
327
339
  })
@@ -286,8 +286,31 @@ export default class RumApplication extends BaseModel {
286
286
  })
287
287
  public description?: string = undefined;
288
288
 
289
+ /*
290
+ * Creatable, never updatable.
291
+ *
292
+ * `create: []` here made the documented "create an application by hand"
293
+ * flow (docs/rum/applications.md) impossible: ModelForm drops any field
294
+ * the caller has no create permission on, so the Dashboard's required
295
+ * "App Identifier" input never rendered, and the POST that followed was
296
+ * rejected by the server with a raw `appIdentifier is required`. The
297
+ * column is required and has no default, so nothing could supply it.
298
+ *
299
+ * `update` stays empty on purpose. This value is the application's
300
+ * identity - telemetry is filed under it and the (projectId,
301
+ * appIdentifier) index is unique - so re-pointing it after the fact would
302
+ * orphan every session, trace and recording already keyed on the old one.
303
+ * Same shape as ServerlessFunction.functionIdentifier.
304
+ */
289
305
  @ColumnAccessControl({
290
- create: [],
306
+ create: [
307
+ Permission.ProjectOwner,
308
+ Permission.ProjectAdmin,
309
+ Permission.ProjectMember,
310
+ Permission.SettingsAdmin,
311
+ Permission.SettingsMember,
312
+ Permission.CreateRumApplication,
313
+ ],
291
314
  read: [
292
315
  Permission.ProjectOwner,
293
316
  Permission.ProjectAdmin,
@@ -308,6 +331,16 @@ export default class RumApplication extends BaseModel {
308
331
  description:
309
332
  "Stable identifier for this application from the service.name OpenTelemetry resource attribute. Identity key for this RUM application.",
310
333
  })
334
+ /*
335
+ * Case-INSENSITIVE uniqueness, which the unique index on
336
+ * (projectId, appIdentifier) does not give: Postgres compares it byte for
337
+ * byte, while RumApplicationService.findOrCreateByAppIdentifier resolves
338
+ * an incoming service.name with QueryHelper.findWithSameText, which
339
+ * lowercases. Without this, someone hand-creating "Storefront-Web" beside
340
+ * an auto-discovered "storefront-web" passes the index and leaves two rows
341
+ * that the case-insensitive lookup resolves arbitrarily.
342
+ */
343
+ @UniqueColumnBy("projectId")
311
344
  @Column({
312
345
  nullable: false,
313
346
  type: ColumnType.ShortText,
@@ -1245,7 +1278,7 @@ export default class RumApplication extends BaseModel {
1245
1278
  type: TableColumnType.Boolean,
1246
1279
  title: "Capture Session Replay User Identity",
1247
1280
  description:
1248
- "When enabled, the raw end-user reference supplied by the host page is stored alongside the recording, so a support engineer can find the session a named customer is complaining about. When off, only a one-way per-project HMAC of it is stored. On by default. Narrower create/update ACL than the other replay settings: this is the switch that turns a pseudonymous recording into an identified one.",
1281
+ "When enabled, the end-user reference supplied by the host page is stored alongside the recording - as a one-way per-project HMAC for lookup and erasure, plus the raw reference behind its own narrower column ACL - so a support engineer can find the session a named customer is complaining about. When off, the reference is never attached to a recording and neither column is stored. (It is still sent once on the policy request, which is how targeted capture matches a named user; it is not persisted.) The reference must be supplied at load time - identify() called later reaches the server only on the session's final chunk, which the header is not rebuilt from. On by default. Narrower create/update ACL than the other replay settings: this is the switch that turns a pseudonymous recording into an identified one.",
1249
1282
  defaultValue: true,
1250
1283
  })
1251
1284
  @Column({
@@ -761,4 +761,47 @@ export default class StatusPageDomain extends BaseModel {
761
761
  default: false, // default is false
762
762
  })
763
763
  public isCustomCertificate?: boolean = undefined;
764
+
765
+ /*
766
+ * When a reissue of this domain's Let's Encrypt certificate was last
767
+ * REQUESTED from the dashboard - not when one last succeeded.
768
+ *
769
+ * This is the whole state behind the reissue cooldown. Certificates come
770
+ * from a Let's Encrypt account shared by every customer on the
771
+ * installation, and a failed order still spends a validation attempt
772
+ * against that account, so the stamp is written before the order is
773
+ * attempted and is left in place whether the order succeeds or fails.
774
+ *
775
+ * Null means this domain has never had a reissue requested; automated
776
+ * renewal never writes here, so the cron keeping a certificate fresh can
777
+ * never be the reason a customer's button is refused.
778
+ */
779
+ @ColumnAccessControl({
780
+ create: [],
781
+ read: [
782
+ Permission.ProjectOwner,
783
+ Permission.ProjectAdmin,
784
+ Permission.ProjectMember,
785
+ Permission.Viewer,
786
+ Permission.StatusPageAdmin,
787
+ Permission.StatusPageMember,
788
+ Permission.StatusPageViewer,
789
+ Permission.ReadStatusPageDomain,
790
+ ],
791
+ update: [],
792
+ })
793
+ @TableColumn({
794
+ required: false,
795
+ computed: true,
796
+ type: TableColumnType.Date,
797
+ title: "Certificate Reissue Requested At",
798
+ description:
799
+ "When a reissue of the SSL certificate for this domain was last requested. Used to rate limit how often a certificate can be reissued.",
800
+ })
801
+ @Column({
802
+ type: ColumnType.Date,
803
+ nullable: true,
804
+ unique: false,
805
+ })
806
+ public certificateReissueRequestedAt?: Date = undefined;
764
807
  }
@@ -238,5 +238,79 @@ export default class DashboardDomainAPI extends BaseAPI<
238
238
  }
239
239
  },
240
240
  );
241
+
242
+ /*
243
+ * Reissue SSL API. Backs the "Reissue SSL Certificate" button in the
244
+ * dashboard, for a customer who wants a fresh certificate now rather than
245
+ * whenever the renewal cron next gets to this domain.
246
+ *
247
+ * The throttle that keeps this from becoming a way to spend the shared
248
+ * Let's Encrypt allowance lives in the service, alongside the write that
249
+ * claims it - a check here would be a check the automated callers do not
250
+ * share, and one an extra caller could forget.
251
+ */
252
+ this.router.get(
253
+ `${new this.entityType().getCrudApiPath()?.toString()}/reissue-ssl/:id`,
254
+ UserMiddleware.getUserMiddleware,
255
+ async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
256
+ try {
257
+ if (!DashboardCNameRecord) {
258
+ return Response.sendErrorResponse(
259
+ req,
260
+ res,
261
+ new BadDataException(
262
+ `Custom Domains not enabled for this
263
+ OneUptime installation. Please contact
264
+ your server admin to enable this
265
+ feature.`,
266
+ ),
267
+ );
268
+ }
269
+
270
+ const databaseProps: DatabaseCommonInteractionProps =
271
+ await CommonAPI.getDatabaseCommonInteractionProps(req);
272
+
273
+ const id: ObjectID = new ObjectID(req.params["id"] as string);
274
+
275
+ /*
276
+ * Scoped by the caller's own props, so a domain in someone else's
277
+ * project is indistinguishable from one that does not exist.
278
+ */
279
+ const domainCount: PositiveNumber =
280
+ await DashboardDomainService.countBy({
281
+ query: {
282
+ _id: id.toString(),
283
+ },
284
+ props: databaseProps,
285
+ });
286
+
287
+ if (domainCount.toNumber() === 0) {
288
+ return Response.sendErrorResponse(
289
+ req,
290
+ res,
291
+ new BadDataException(
292
+ "The domain does not exist or user does not have access to it.",
293
+ ),
294
+ );
295
+ }
296
+
297
+ logger.debug(
298
+ "Reissuing SSL",
299
+ getLogAttributesFromRequest(req as OneUptimeRequest),
300
+ );
301
+
302
+ await DashboardDomainService.reissueCert(id);
303
+
304
+ logger.debug(
305
+ "SSL reissued for domain id - " + id.toString(),
306
+ getLogAttributesFromRequest(req as OneUptimeRequest),
307
+ );
308
+
309
+ return Response.sendEmptySuccessResponse(req, res);
310
+ } catch (e) {
311
+ next(e);
312
+ }
313
+ },
314
+ );
241
315
  }
242
316
  }
@@ -32,14 +32,6 @@ import ObjectID from "../../Types/ObjectID";
32
32
  import WorkspaceUserAuthTokenService from "../Services/WorkspaceUserAuthTokenService";
33
33
  import WorkspaceUserAuthToken from "../../Models/DatabaseModels/WorkspaceUserAuthToken";
34
34
  import WorkspaceType from "../../Types/Workspace/WorkspaceType";
35
- import MicrosoftTeamsAuthAction, {
36
- MicrosoftTeamsRequest,
37
- } from "../Utils/Workspace/MicrosoftTeams/Actions/Auth";
38
- import MicrosoftTeamsIncidentActions from "../Utils/Workspace/MicrosoftTeams/Actions/Incident";
39
- import MicrosoftTeamsAlertActions from "../Utils/Workspace/MicrosoftTeams/Actions/Alert";
40
- import MicrosoftTeamsScheduledMaintenanceActions from "../Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance";
41
- import MicrosoftTeamsMonitorActions from "../Utils/Workspace/MicrosoftTeams/Actions/Monitor";
42
- import MicrosoftTeamsOnCallDutyActions from "../Utils/Workspace/MicrosoftTeams/Actions/OnCallDutyPolicy";
43
35
  import MicrosoftTeamsUtil from "../Utils/Workspace/MicrosoftTeams/MicrosoftTeams";
44
36
  import archiver, { Archiver } from "archiver";
45
37
  import LocalFile from "../Utils/LocalFile";
@@ -927,63 +919,78 @@ export default class MicrosoftTeamsAPI {
927
919
  },
928
920
  );
929
921
 
930
- // Microsoft Teams webhook endpoint for interactive messages (legacy)
931
- router.post(
932
- "/microsoft-teams/webhook",
933
- async (req: ExpressRequest, res: ExpressResponse) => {
934
- logger.debug(
935
- "Microsoft Teams Webhook Request: ",
936
- getLogAttributesFromRequest(req as any),
937
- );
938
- logger.debug(req.body, getLogAttributesFromRequest(req as any));
939
-
940
- try {
941
- const authResult: MicrosoftTeamsRequest =
942
- await MicrosoftTeamsAuthAction.isAuthorized({
943
- req: req,
944
- });
945
-
946
- logger.debug(
947
- "Microsoft Teams Auth Result: ",
948
- getLogAttributesFromRequest(req as any),
949
- );
950
- logger.debug(authResult, getLogAttributesFromRequest(req as any));
951
-
952
- if (authResult.isAuthorized === false) {
953
- return Response.sendTextResponse(req, res, "");
954
- }
955
-
956
- // Handle different types of Teams activities
957
- const activity: JSONObject = req.body as JSONObject;
958
- const activityType: string = activity["type"] as string;
959
-
960
- if (activityType === "message") {
961
- // Handle bot mentions or direct messages
962
- return MicrosoftTeamsAPI.handleMessageActivity(
963
- req,
964
- res,
965
- authResult,
966
- activity,
967
- );
968
- } else if (activityType === "invoke") {
969
- // Handle adaptive card actions
970
- return MicrosoftTeamsAPI.handleInvokeActivity(
971
- req,
972
- res,
973
- authResult,
974
- activity,
975
- );
976
- }
977
-
978
- return Response.sendTextResponse(req, res, "");
979
- } catch (error) {
980
- logger.error(
981
- "Error processing Teams webhook:",
982
- getLogAttributesFromRequest(req as any),
983
- );
984
- logger.error(error, getLogAttributesFromRequest(req as any));
985
- return Response.sendTextResponse(req, res, "");
986
- }
922
+ /*
923
+ * The same path, answering the browser.
924
+ *
925
+ * Azure Bot Service only ever POSTs here, so for a long time GET fell
926
+ * through to the catch-all 404 in StartServer and answered
927
+ * "Page not found - /api/microsoft-bot/messages". That is the single
928
+ * cheapest thing an admin can do to check their messaging endpoint, and
929
+ * OneUptime replied with the words for "this route does not exist" — so
930
+ * admins concluded the route had been dropped from the build and went
931
+ * looking for a regression, while the actual fault (Azure cannot reach
932
+ * this deployment) sat untouched. The 404 was true of the method and false
933
+ * of the endpoint, and nothing in the response said which.
934
+ *
935
+ * 405 is the honest answer, and it is honest in a way a human reads at a
936
+ * glance: the route is here, the deployment is reachable from wherever the
937
+ * request came from, and POST is what it wants. Everything else in the
938
+ * body exists to redirect the next hour of debugging away from OneUptime's
939
+ * routing table and towards the network path Azure has to traverse.
940
+ */
941
+ router.get(
942
+ "/microsoft-bot/messages",
943
+ (_req: ExpressRequest, res: ExpressResponse) => {
944
+ res.setHeader("Allow", "POST");
945
+
946
+ /*
947
+ * Written straight onto the response rather than through
948
+ * Response.sendJsonObjectResponse, whose ?output-type=csv branch
949
+ * answers 200 regardless of the status code it was handed. The status
950
+ * code is the entire point of this route — it is what distinguishes
951
+ * "wrong method" from "no such route" — so no query parameter gets to
952
+ * rewrite it.
953
+ */
954
+ res.status(405).json({
955
+ status:
956
+ "This is the OneUptime Microsoft Teams bot messaging endpoint. It exists, and it accepts POST only.",
957
+ allow: ["POST"],
958
+ messagingEndpoint: `${AppApiClientUrl.toString()}/microsoft-bot/messages`,
959
+ /*
960
+ * Stated as a fact about this response rather than as advice,
961
+ * because it is the one thing the admin has actually proven by
962
+ * getting here and it is easy to under-read.
963
+ */
964
+ whatThisProves:
965
+ "You reached OneUptime. This endpoint is registered and this deployment served your request, so the messaging endpoint is not missing.",
966
+ whatThisDoesNotProve:
967
+ "That Azure Bot Service can reach this URL. Azure calls it from the public internet, and your browser or terminal may not be on the same path.",
968
+ /*
969
+ * A 404 on this path does NOT mean the request failed to arrive, and
970
+ * saying so would repeat the mistake this endpoint exists to end.
971
+ * OneUptime's own catch-all (StartServer's app.get("*")) answers an
972
+ * unmatched GET with a 404 whose body is
973
+ * {"message":"Page not found - <path>"} — 58 bytes for this path,
974
+ * which is exactly the `404 58` an admin sees in their access log.
975
+ * That 404 is proof the request arrived, not proof it did not. Only
976
+ * the body separates it from a proxy's own 404, so point at the body.
977
+ */
978
+ ifYouGetA404InsteadOfThis:
979
+ 'Read the body, not just the status code. A body of {"message":"Page not found - /api/microsoft-bot/messages"} comes from OneUptime itself, which means the request DID arrive: either this deployment predates this 405 response (the path was POST-only, so a GET fell through to the generic not-found handler), or something in front of OneUptime rewrote the path and stripped the /api prefix before the app saw it. An HTML error page from nginx, your ingress or a load balancer means the opposite — the request never reached OneUptime.',
980
+ /*
981
+ * Ordered by what actually goes wrong on self-hosted deployments.
982
+ * Outbound-works-inbound-fails leads because it is the state that
983
+ * makes people doubt the route: alerts arrive in Teams, so the
984
+ * integration looks live, and only the interactive half is dead.
985
+ */
986
+ ifTeamsSaysUnableToReachApp: [
987
+ "Working outbound alerts prove nothing here. OneUptime posts cards by calling Microsoft, which needs no inbound access. Card buttons, bot commands and chat registration all travel the other way — Azure Bot Service POSTs to this URL — and that is the direction that is failing.",
988
+ "Confirm this exact URL is set as the messaging endpoint on the Azure Bot resource, then check that it resolves publicly. A private DNS name or an internal-only ingress is the most common cause.",
989
+ "Azure requires HTTPS with a publicly trusted certificate served with its full chain. A self-signed certificate, an internal CA, or a missing intermediate fails the TLS handshake before OneUptime ever sees the request, so nothing appears in your access log.",
990
+ "Then look for the POST, not the 404, in your access log: `grep 'POST /api/microsoft-bot/messages' <access log>`. No POST lines at all means Azure never got through, and the fault is the network path rather than OneUptime.",
991
+ ],
992
+ nextStep: `GET ${AppApiClientUrl.toString()}/microsoft-bot/test to see this deployment's bot id, and compare it with the bot id of the OneUptime app package installed in Microsoft Teams.`,
993
+ });
987
994
  },
988
995
  );
989
996
 
@@ -1031,6 +1038,15 @@ export default class MicrosoftTeamsAPI {
1031
1038
  notVerified: [
1032
1039
  "That an Azure Bot resource exists for this client id",
1033
1040
  "That the Azure Bot's messaging endpoint points at this deployment",
1041
+ /*
1042
+ * Reachability is listed separately from the endpoint being
1043
+ * configured, because they fail separately and look identical from
1044
+ * in here. A correctly configured endpoint on a deployment Azure
1045
+ * cannot dial produces working outbound alerts and a completely
1046
+ * dead bot, which reads as a half-broken integration rather than a
1047
+ * network problem.
1048
+ */
1049
+ "That Azure Bot Service can actually reach this deployment over the public internet — outbound notifications work without it, so a working alert does not test this",
1034
1050
  "That the Azure Bot has the Microsoft Teams channel enabled",
1035
1051
  "That the client secret is valid and has not expired",
1036
1052
  "That the Teams app package installed in your teams was built from this deployment",
@@ -1238,119 +1254,4 @@ export default class MicrosoftTeamsAPI {
1238
1254
 
1239
1255
  return router;
1240
1256
  }
1241
-
1242
- private static async handleMessageActivity(
1243
- _req: ExpressRequest,
1244
- res: ExpressResponse,
1245
- authResult: MicrosoftTeamsRequest,
1246
- activity: JSONObject,
1247
- ): Promise<void> {
1248
- // Handle direct messages to bot or @mentions
1249
- const messageText: string = (activity["text"] as string) || "";
1250
- const from: JSONObject = activity["from"] as JSONObject;
1251
-
1252
- if (messageText.toLowerCase().includes("help")) {
1253
- // Send help message
1254
- const helpMessage: any = {
1255
- _type: "WorkspacePayloadText",
1256
- text: "Hello! I'm the OneUptime bot. I can help you:\n\n• Get notifications about incidents\n• Acknowledge alerts\n• View system status\n\nType 'status' to see current system status.",
1257
- };
1258
-
1259
- await MicrosoftTeamsUtil.sendDirectMessageToUser({
1260
- authToken: authResult.authToken,
1261
- workspaceUserId: from["id"] as string,
1262
- messageBlocks: [helpMessage],
1263
- });
1264
- }
1265
-
1266
- Response.sendTextResponse(_req, res, "");
1267
- }
1268
-
1269
- private static async handleInvokeActivity(
1270
- req: ExpressRequest,
1271
- res: ExpressResponse,
1272
- authResult: MicrosoftTeamsRequest,
1273
- _activity: JSONObject,
1274
- ): Promise<void> {
1275
- /*
1276
- * Handle adaptive card button clicks
1277
- * const value: JSONObject = activity["value"] as JSONObject;
1278
- * const actionType: string = value["action"] as string;
1279
- */
1280
-
1281
- for (const action of authResult.actions || []) {
1282
- if (!action.actionType) {
1283
- continue;
1284
- }
1285
-
1286
- if (
1287
- MicrosoftTeamsIncidentActions.isIncidentAction({
1288
- actionType: action.actionType,
1289
- })
1290
- ) {
1291
- return MicrosoftTeamsIncidentActions.handleIncidentAction({
1292
- teamsRequest: authResult,
1293
- action: action,
1294
- req: req,
1295
- res: res,
1296
- });
1297
- }
1298
-
1299
- if (
1300
- MicrosoftTeamsAlertActions.isAlertAction({
1301
- actionType: action.actionType,
1302
- })
1303
- ) {
1304
- return MicrosoftTeamsAlertActions.handleAlertAction({
1305
- teamsRequest: authResult,
1306
- action: action,
1307
- req: req,
1308
- res: res,
1309
- });
1310
- }
1311
-
1312
- if (
1313
- MicrosoftTeamsScheduledMaintenanceActions.isScheduledMaintenanceAction({
1314
- actionType: action.actionType,
1315
- })
1316
- ) {
1317
- return MicrosoftTeamsScheduledMaintenanceActions.handleScheduledMaintenanceAction(
1318
- {
1319
- teamsRequest: authResult,
1320
- action: action,
1321
- req: req,
1322
- res: res,
1323
- },
1324
- );
1325
- }
1326
-
1327
- if (
1328
- MicrosoftTeamsMonitorActions.isMonitorAction({
1329
- actionType: action.actionType,
1330
- })
1331
- ) {
1332
- return MicrosoftTeamsMonitorActions.handleMonitorAction({
1333
- teamsRequest: authResult,
1334
- action: action,
1335
- req: req,
1336
- res: res,
1337
- });
1338
- }
1339
-
1340
- if (
1341
- MicrosoftTeamsOnCallDutyActions.isOnCallDutyAction({
1342
- actionType: action.actionType,
1343
- })
1344
- ) {
1345
- return MicrosoftTeamsOnCallDutyActions.handleOnCallDutyAction({
1346
- teamsRequest: authResult,
1347
- action: action,
1348
- req: req,
1349
- res: res,
1350
- });
1351
- }
1352
- }
1353
-
1354
- Response.sendTextResponse(req, res, "");
1355
- }
1356
1257
  }