@oneuptime/common 12.0.14 → 12.0.15

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 (195) hide show
  1. package/Models/DatabaseModels/GlobalOidc.ts +20 -0
  2. package/Models/DatabaseModels/GlobalSso.ts +20 -0
  3. package/Models/DatabaseModels/MarketingConversion.ts +19 -9
  4. package/Models/DatabaseModels/NetworkDeviceLinkRule.ts +46 -0
  5. package/Models/DatabaseModels/StatusPage.ts +7 -4
  6. package/Server/API/StatusPageAPI.ts +29 -4
  7. package/Server/EnvironmentConfig.ts +7 -0
  8. package/Server/Infrastructure/Postgres/SchemaMigrations/1787800000000-AddScopeToNetworkDeviceLinkRule.ts +19 -0
  9. package/Server/Infrastructure/Postgres/SchemaMigrations/1787900000000-AddRestrictToAttachedProjectsToGlobalSso.ts +40 -0
  10. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  11. package/Server/Middleware/SlackAuthorization.ts +43 -6
  12. package/Server/Middleware/UserAuthorization.ts +236 -26
  13. package/Server/Middleware/WhatsAppAuthorization.ts +43 -3
  14. package/Server/Services/GlobalOidcProjectService.ts +126 -1
  15. package/Server/Services/GlobalOidcService.ts +132 -0
  16. package/Server/Services/GlobalSsoProjectService.ts +126 -1
  17. package/Server/Services/GlobalSsoService.ts +132 -0
  18. package/Server/Services/UserEmailService.ts +4 -4
  19. package/Server/Utils/CronTab.ts +73 -0
  20. package/Server/Utils/GlobalSsoAuthorization.ts +175 -0
  21. package/Server/Utils/Marketing/ConversionUploadProvider.ts +35 -5
  22. package/Server/Utils/Marketing/Providers/GoogleAds.ts +1 -1
  23. package/Server/Utils/Marketing/Providers/LinkedIn.ts +1 -1
  24. package/Server/Utils/Marketing/Providers/Meta.ts +1 -1
  25. package/Server/Utils/Marketing/Providers/MicrosoftAds.ts +1 -1
  26. package/Server/Utils/Marketing/Providers/Reddit.ts +1 -1
  27. package/Server/Utils/Monitor/Criteria/APIRequestCriteria.ts +48 -33
  28. package/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.ts +35 -30
  29. package/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.ts +33 -28
  30. package/Server/Utils/Monitor/Criteria/EvaluateOverTime.ts +475 -47
  31. package/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.ts +35 -30
  32. package/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.ts +34 -28
  33. package/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.ts +33 -28
  34. package/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.ts +69 -40
  35. package/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.ts +35 -29
  36. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +8 -0
  37. package/Server/Utils/Monitor/MonitorResource.ts +161 -21
  38. package/Server/Utils/StatusPageContentSecurityPolicy.ts +56 -0
  39. package/Server/Utils/StatusPageCustomizationAccess.ts +117 -0
  40. package/Tests/Server/API/StatusPageCustomizationOrigin.test.ts +348 -0
  41. package/Tests/Server/Infrastructure/Postgres/FixTotpOtpUrlAlgorithmMigration.test.ts +9 -59
  42. package/Tests/Server/Infrastructure/Postgres/SchemaMigrationsOrdering.test.ts +227 -0
  43. package/Tests/Server/Middleware/SlackAuthorization.test.ts +149 -0
  44. package/Tests/Server/Middleware/UserAuthorization.test.ts +27 -13
  45. package/Tests/Server/Middleware/UserAuthorizationGlobalSsoGovernance.test.ts +1455 -0
  46. package/Tests/Server/Middleware/UserAuthorizationSSOProvider.test.ts +91 -2
  47. package/Tests/Server/Middleware/WhatsAppAuthorization.test.ts +233 -0
  48. package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +21 -8
  49. package/Tests/Server/Services/GlobalSsoProviderAuthorization.test.ts +1242 -0
  50. package/Tests/Server/Services/MonitorResourceProbeAgreementHydration.test.ts +459 -0
  51. package/Tests/Server/Services/MonitorResourceProbeAgreementReuse.test.ts +10 -0
  52. package/Tests/Server/Types/AnalyticsDatabase/AggregateUtilBucketTimestamp.test.ts +416 -0
  53. package/Tests/Server/Types/Database/QueryHelperManyToManyAndEmptyValues.test.ts +551 -0
  54. package/Tests/Server/Types/Database/SelectUtil.test.ts +348 -0
  55. package/Tests/Server/Utils/CronTab.test.ts +116 -0
  56. package/Tests/Server/Utils/GlobalSSOToken.test.ts +581 -0
  57. package/Tests/Server/Utils/GlobalSsoAuthorization.test.ts +1401 -0
  58. package/Tests/Server/Utils/Marketing/AdUploadableConversionTypes.test.ts +254 -0
  59. package/Tests/Server/Utils/Marketing/ConversionUploadProvider.test.ts +1 -1
  60. package/Tests/Server/Utils/Monitor/Criteria/APIRequestCriteriaEvaluateOverTime.test.ts +508 -0
  61. package/Tests/Server/Utils/Monitor/Criteria/APIRequestCriteriaPortTimings.test.ts +30 -9
  62. package/Tests/Server/Utils/Monitor/Criteria/EvaluateOverTime.test.ts +987 -0
  63. package/Tests/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.test.ts +916 -0
  64. package/Tests/Server/Utils/Monitor/Criteria/ProfileMonitorCriteria.test.ts +382 -0
  65. package/Tests/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.test.ts +1219 -0
  66. package/Tests/Server/Utils/Monitor/MonitorResourceIngestedStepSelection.test.ts +86 -0
  67. package/Tests/Server/Utils/StatusPageContentSecurityPolicy.test.ts +136 -0
  68. package/Tests/Server/Utils/StatusPageCustomizationAccess.test.ts +223 -0
  69. package/Tests/Types/Monitor/MonitorStepConfigHelpers.test.ts +165 -0
  70. package/Tests/Types/NetworkDevice/NetworkDeviceLinkRuleScope.test.ts +215 -0
  71. package/Tests/UI/Components/Forms/FormSubmitAnalytics.test.tsx +206 -0
  72. package/Tests/UI/Utils/AIChatExport/ConversationMarkdown.test.ts +774 -0
  73. package/Tests/UI/Utils/AIChatExport/MarkdownBlocks.test.ts +791 -0
  74. package/Tests/UI/Utils/AIChatExport/MarkdownSafety.test.ts +544 -0
  75. package/Tests/Utils/Analytics.test.ts +187 -0
  76. package/Tests/Utils/Monitor/MonitorMetricType.test.ts +80 -0
  77. package/Tests/Utils/Monitor/NetworkDeviceLinkRuleUtil.test.ts +1291 -0
  78. package/Tests/Utils/NetworkDiscovery/UnclaimedScanDiagnosis.test.ts +214 -0
  79. package/Types/Marketing/MarketingConversion.ts +12 -0
  80. package/Types/Monitor/CriteriaFilter.ts +9 -0
  81. package/Types/NetworkDevice/NetworkDeviceLinkRuleScope.ts +51 -0
  82. package/UI/Components/FormModal/BasicFormModal.tsx +12 -0
  83. package/UI/Components/Forms/BasicForm.tsx +13 -1
  84. package/UI/Components/Forms/BasicModelForm.tsx +2 -1
  85. package/UI/Components/Forms/ModelForm.tsx +10 -1
  86. package/UI/Components/Forms/Utils/FormAnalyticsName.ts +39 -0
  87. package/UI/Components/ModelFormModal/ModelFormModal.tsx +6 -1
  88. package/Utils/Analytics.ts +55 -2
  89. package/Utils/Monitor/MonitorMetricType.ts +40 -0
  90. package/Utils/Monitor/NetworkDeviceLinkRuleUtil.ts +587 -37
  91. package/Utils/NetworkDiscovery/UnclaimedScanDiagnosis.ts +115 -0
  92. package/build/dist/Models/DatabaseModels/GlobalOidc.js +21 -0
  93. package/build/dist/Models/DatabaseModels/GlobalOidc.js.map +1 -1
  94. package/build/dist/Models/DatabaseModels/GlobalSso.js +21 -0
  95. package/build/dist/Models/DatabaseModels/GlobalSso.js.map +1 -1
  96. package/build/dist/Models/DatabaseModels/MarketingConversion.js +20 -10
  97. package/build/dist/Models/DatabaseModels/MarketingConversion.js.map +1 -1
  98. package/build/dist/Models/DatabaseModels/NetworkDeviceLinkRule.js +47 -0
  99. package/build/dist/Models/DatabaseModels/NetworkDeviceLinkRule.js.map +1 -1
  100. package/build/dist/Models/DatabaseModels/StatusPage.js +4 -4
  101. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  102. package/build/dist/Server/API/StatusPageAPI.js +25 -4
  103. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  104. package/build/dist/Server/EnvironmentConfig.js +6 -0
  105. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  106. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787800000000-AddScopeToNetworkDeviceLinkRule.js +12 -0
  107. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787800000000-AddScopeToNetworkDeviceLinkRule.js.map +1 -0
  108. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787900000000-AddRestrictToAttachedProjectsToGlobalSso.js +29 -0
  109. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787900000000-AddRestrictToAttachedProjectsToGlobalSso.js.map +1 -0
  110. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  111. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  112. package/build/dist/Server/Middleware/SlackAuthorization.js +28 -4
  113. package/build/dist/Server/Middleware/SlackAuthorization.js.map +1 -1
  114. package/build/dist/Server/Middleware/UserAuthorization.js +197 -22
  115. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  116. package/build/dist/Server/Middleware/WhatsAppAuthorization.js +32 -2
  117. package/build/dist/Server/Middleware/WhatsAppAuthorization.js.map +1 -1
  118. package/build/dist/Server/Services/GlobalOidcProjectService.js +105 -0
  119. package/build/dist/Server/Services/GlobalOidcProjectService.js.map +1 -1
  120. package/build/dist/Server/Services/GlobalOidcService.js +127 -0
  121. package/build/dist/Server/Services/GlobalOidcService.js.map +1 -1
  122. package/build/dist/Server/Services/GlobalSsoProjectService.js +105 -0
  123. package/build/dist/Server/Services/GlobalSsoProjectService.js.map +1 -1
  124. package/build/dist/Server/Services/GlobalSsoService.js +127 -0
  125. package/build/dist/Server/Services/GlobalSsoService.js.map +1 -1
  126. package/build/dist/Server/Services/UserEmailService.js +4 -4
  127. package/build/dist/Server/Utils/CronTab.js +66 -0
  128. package/build/dist/Server/Utils/CronTab.js.map +1 -1
  129. package/build/dist/Server/Utils/GlobalSsoAuthorization.js +112 -0
  130. package/build/dist/Server/Utils/GlobalSsoAuthorization.js.map +1 -0
  131. package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js +17 -1
  132. package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js.map +1 -1
  133. package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js +1 -1
  134. package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js.map +1 -1
  135. package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js +1 -1
  136. package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js.map +1 -1
  137. package/build/dist/Server/Utils/Marketing/Providers/Meta.js +1 -1
  138. package/build/dist/Server/Utils/Marketing/Providers/Meta.js.map +1 -1
  139. package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js +1 -1
  140. package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js.map +1 -1
  141. package/build/dist/Server/Utils/Marketing/Providers/Reddit.js +1 -1
  142. package/build/dist/Server/Utils/Marketing/Providers/Reddit.js.map +1 -1
  143. package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js +37 -35
  144. package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js.map +1 -1
  145. package/build/dist/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.js +21 -25
  146. package/build/dist/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.js.map +1 -1
  147. package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js +20 -22
  148. package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js.map +1 -1
  149. package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js +370 -42
  150. package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js.map +1 -1
  151. package/build/dist/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.js +21 -25
  152. package/build/dist/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.js.map +1 -1
  153. package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js +21 -25
  154. package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js.map +1 -1
  155. package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js +21 -25
  156. package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js.map +1 -1
  157. package/build/dist/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.js +64 -52
  158. package/build/dist/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.js.map +1 -1
  159. package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js +25 -29
  160. package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js.map +1 -1
  161. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +8 -0
  162. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  163. package/build/dist/Server/Utils/Monitor/MonitorResource.js +123 -23
  164. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  165. package/build/dist/Server/Utils/StatusPageContentSecurityPolicy.js +39 -0
  166. package/build/dist/Server/Utils/StatusPageContentSecurityPolicy.js.map +1 -0
  167. package/build/dist/Server/Utils/StatusPageCustomizationAccess.js +85 -0
  168. package/build/dist/Server/Utils/StatusPageCustomizationAccess.js.map +1 -0
  169. package/build/dist/Types/Marketing/MarketingConversion.js +10 -0
  170. package/build/dist/Types/Marketing/MarketingConversion.js.map +1 -1
  171. package/build/dist/Types/Monitor/CriteriaFilter.js +1 -0
  172. package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
  173. package/build/dist/Types/NetworkDevice/NetworkDeviceLinkRuleScope.js +46 -0
  174. package/build/dist/Types/NetworkDevice/NetworkDeviceLinkRuleScope.js.map +1 -0
  175. package/build/dist/UI/Components/FormModal/BasicFormModal.js +2 -1
  176. package/build/dist/UI/Components/FormModal/BasicFormModal.js.map +1 -1
  177. package/build/dist/UI/Components/Forms/BasicForm.js +10 -1
  178. package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
  179. package/build/dist/UI/Components/Forms/BasicModelForm.js +2 -1
  180. package/build/dist/UI/Components/Forms/BasicModelForm.js.map +1 -1
  181. package/build/dist/UI/Components/Forms/ModelForm.js +4 -1
  182. package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
  183. package/build/dist/UI/Components/Forms/Utils/FormAnalyticsName.js +33 -0
  184. package/build/dist/UI/Components/Forms/Utils/FormAnalyticsName.js.map +1 -0
  185. package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js +2 -1
  186. package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
  187. package/build/dist/Utils/Analytics.js +50 -2
  188. package/build/dist/Utils/Analytics.js.map +1 -1
  189. package/build/dist/Utils/Monitor/MonitorMetricType.js +37 -0
  190. package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
  191. package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js +318 -37
  192. package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js.map +1 -1
  193. package/build/dist/Utils/NetworkDiscovery/UnclaimedScanDiagnosis.js +87 -0
  194. package/build/dist/Utils/NetworkDiscovery/UnclaimedScanDiagnosis.js.map +1 -0
  195. package/package.json +1 -1
@@ -235,6 +235,26 @@ export default class GlobalOIDC extends BaseModel {
235
235
  })
236
236
  public disableSignUpWithSso?: boolean = undefined;
237
237
 
238
+ @ColumnAccessControl({
239
+ create: [],
240
+ read: [],
241
+ update: [],
242
+ })
243
+ @TableColumn({
244
+ isDefaultValueColumn: true,
245
+ type: TableColumnType.Boolean,
246
+ title: "Restrict to Attached Projects",
247
+ description:
248
+ "When enabled, a login through this provider only satisfies SSO enforcement for the projects attached below. Leave this off (the default) and the provider satisfies SSO enforcement for every project the user is a member of, which is how attachments have always behaved - they are the provisioning allow-list, not an access boundary. Turning this on narrows access for people already signed in.",
249
+ defaultValue: false,
250
+ example: true,
251
+ })
252
+ @Column({
253
+ type: ColumnType.Boolean,
254
+ default: false,
255
+ })
256
+ public restrictToAttachedProjects?: boolean = undefined;
257
+
238
258
  @ColumnAccessControl({
239
259
  create: [],
240
260
  read: [],
@@ -196,6 +196,26 @@ export default class GlobalSSO extends BaseModel {
196
196
  })
197
197
  public disableSignUpWithSso?: boolean = undefined;
198
198
 
199
+ @ColumnAccessControl({
200
+ create: [],
201
+ read: [],
202
+ update: [],
203
+ })
204
+ @TableColumn({
205
+ isDefaultValueColumn: true,
206
+ type: TableColumnType.Boolean,
207
+ title: "Restrict to Attached Projects",
208
+ description:
209
+ "When enabled, a login through this provider only satisfies SSO enforcement for the projects attached below. Leave this off (the default) and the provider satisfies SSO enforcement for every project the user is a member of, which is how attachments have always behaved - they are the provisioning allow-list, not an access boundary. Turning this on narrows access for people already signed in.",
210
+ defaultValue: false,
211
+ example: true,
212
+ })
213
+ @Column({
214
+ type: ColumnType.Boolean,
215
+ default: false,
216
+ })
217
+ public restrictToAttachedProjects?: boolean = undefined;
218
+
199
219
  @ColumnAccessControl({
200
220
  create: [],
201
221
  read: [],
@@ -14,11 +14,13 @@ import ObjectID from "../../Types/ObjectID";
14
14
  import { Column, Entity, Index } from "typeorm";
15
15
 
16
16
  /*
17
- * Internal table (no API access) recording ad-attributed conversions —
18
- * signups and paid subscriptions from visitors that carried ad click IDs
19
- * and the status of uploading each of them to ad platforms (Google Ads
20
- * offline click conversions, Meta Conversions API). Written and read only by
21
- * the MarketingConversions worker job.
17
+ * Internal table (no API access) recording server-confirmed conversions —
18
+ * signups, booked meetings and paid subscriptions, along with any ad click IDs
19
+ * the converting visitor carried and the status of uploading the
20
+ * ad-uploadable ones to ad platforms (Google Ads offline click conversions,
21
+ * Meta Conversions API). Browser analytics never write this table: rows are
22
+ * written by the signup/subscription flows and the Cal.com webhook, and read
23
+ * by the MarketingConversions worker job.
22
24
  */
23
25
  @TableAccessControl({
24
26
  create: [],
@@ -33,11 +35,18 @@ import { Column, Entity, Index } from "typeorm";
33
35
  pluralName: "Marketing Conversions",
34
36
  icon: IconProp.ChartBar,
35
37
  tableDescription:
36
- "Ad-attributed conversions (signups, paid subscriptions) and their upload status to ad platforms for offline conversion tracking.",
38
+ "Server-confirmed conversions (signups, meetings booked, paid subscriptions) and their upload status to ad platforms for offline conversion tracking.",
37
39
  })
38
40
  @Entity({
39
41
  name: "MarketingConversion",
40
42
  })
43
+ /*
44
+ * One conversion of each type per user/project. Postgres treats NULLs as
45
+ * distinct, so these indexes only constrain rows that actually carry a user or
46
+ * project id — conversion types with neither (a booked meeting from an
47
+ * anonymous visitor) are de-duplicated by their deterministic primary key
48
+ * instead.
49
+ */
41
50
  @Index("uq_marketing_conversion_type_user", ["conversionType", "userId"], {
42
51
  unique: true,
43
52
  })
@@ -58,7 +67,7 @@ export default class MarketingConversion extends BaseModel {
58
67
  type: TableColumnType.ShortText,
59
68
  required: true,
60
69
  title: "Conversion Type",
61
- description: "SignUp or PaidSubscription.",
70
+ description: "SignUp, MeetingBooked or PaidSubscription.",
62
71
  })
63
72
  @Column({
64
73
  type: ColumnType.ShortText,
@@ -77,7 +86,8 @@ export default class MarketingConversion extends BaseModel {
77
86
  type: TableColumnType.ObjectID,
78
87
  required: false,
79
88
  title: "User ID",
80
- description: "User this conversion belongs to (SignUp conversions).",
89
+ description:
90
+ "User this conversion belongs to (SignUp conversions). Null when the conversion has no OneUptime user yet, as with a booked meeting.",
81
91
  })
82
92
  @Column({
83
93
  type: ColumnType.ObjectID,
@@ -170,7 +180,7 @@ export default class MarketingConversion extends BaseModel {
170
180
  required: false,
171
181
  title: "Conversion Value (USD Cents)",
172
182
  description:
173
- "Conversion value in USD cents (monthly recurring revenue for paid subscriptions). Null when unknown (custom pricing) or not applicable (signups).",
183
+ "Conversion value in USD cents (monthly recurring revenue for paid subscriptions). Null when unknown (custom pricing) or not applicable (signups, booked meetings).",
174
184
  })
175
185
  @Column({
176
186
  type: ColumnType.Number,
@@ -1,6 +1,7 @@
1
1
  import Label from "./Label";
2
2
  import Project from "./Project";
3
3
  import User from "./User";
4
+ import NetworkDeviceLinkRuleScope from "../../Types/NetworkDevice/NetworkDeviceLinkRuleScope";
4
5
  import BaseModel from "./DatabaseBaseModel/DatabaseBaseModel";
5
6
  import Route from "../../Types/API/Route";
6
7
  import ColumnAccessControl from "../../Types/Database/AccessControl/ColumnAccessControl";
@@ -42,6 +43,15 @@ import {
42
43
  * cabling. Both cases are reported rather than silently producing an empty
43
44
  * map — see NetworkDeviceLinkRuleUtil.
44
45
  *
46
+ * "Exactly one" needs a universe, and `scope` names it. By default the
47
+ * question is asked across the whole project, which is what every rule
48
+ * written before that column existed means. Setting it to Site asks the same
49
+ * question once per site instead, so one rule draws the same router-to-switch
50
+ * star in every building rather than reporting fourteen candidate routers and
51
+ * drawing nothing anywhere. A site-scoped rule looks only inside each device's
52
+ * OWN site — it never reaches up to a parent site — and skips devices that
53
+ * have no site at all, reporting how many it walked past.
54
+ *
45
55
  * Rules are evaluated when the topology is built, not materialised into
46
56
  * NetworkDeviceLink rows: relabelling a device then shows up on the next
47
57
  * refresh, and there is nothing to garbage-collect when a rule is deleted.
@@ -342,6 +352,42 @@ export default class NetworkDeviceLinkRule extends BaseModel {
342
352
  })
343
353
  public parentDeviceLabels?: Array<Label> = undefined;
344
354
 
355
+ @ColumnAccessControl({
356
+ create: [
357
+ Permission.ProjectOwner,
358
+ Permission.ProjectAdmin,
359
+ Permission.CreateNetworkDeviceLinkRule,
360
+ ],
361
+ read: [
362
+ Permission.ProjectOwner,
363
+ Permission.ProjectAdmin,
364
+ Permission.ProjectMember,
365
+ Permission.Viewer,
366
+ Permission.ReadNetworkDeviceLinkRule,
367
+ ],
368
+ update: [
369
+ Permission.ProjectOwner,
370
+ Permission.ProjectAdmin,
371
+ Permission.EditNetworkDeviceLinkRule,
372
+ ],
373
+ })
374
+ @TableColumn({
375
+ required: false,
376
+ type: TableColumnType.ShortText,
377
+ canReadOnRelationQuery: true,
378
+ title: "Parent Scope",
379
+ description:
380
+ "How wide the 'exactly one parent device' question is asked. Project (the default) looks for one parent across the whole project. Site asks once per site, so the same rule can draw an uplink in every building. Rules created before this existed are Project.",
381
+ example: "Project",
382
+ })
383
+ @Column({
384
+ nullable: true,
385
+ type: ColumnType.ShortText,
386
+ length: ColumnLength.ShortText,
387
+ default: NetworkDeviceLinkRuleScope.Project,
388
+ })
389
+ public scope?: string = undefined;
390
+
345
391
  @ColumnAccessControl({
346
392
  create: [
347
393
  Permission.ProjectOwner,
@@ -928,7 +928,8 @@ export default class StatusPage extends BaseModel {
928
928
  required: false,
929
929
  type: TableColumnType.HTML,
930
930
  title: "Header HTML",
931
- description: "Status Page Custom HTML Header",
931
+ description:
932
+ "Status Page Custom HTML Header. Served only from a verified custom domain.",
932
933
  })
933
934
  @Column({
934
935
  nullable: true,
@@ -973,7 +974,8 @@ export default class StatusPage extends BaseModel {
973
974
  required: false,
974
975
  type: TableColumnType.HTML,
975
976
  title: "Footer HTML",
976
- description: "Status Page Custom HTML Footer",
977
+ description:
978
+ "Status Page Custom HTML Footer. Served only from a verified custom domain.",
977
979
  })
978
980
  @Column({
979
981
  nullable: true,
@@ -1018,7 +1020,8 @@ export default class StatusPage extends BaseModel {
1018
1020
  required: false,
1019
1021
  type: TableColumnType.CSS,
1020
1022
  title: "CSS",
1021
- description: "Status Page Custom CSS Header",
1023
+ description:
1024
+ "Status Page Custom CSS. Served only from a verified custom domain.",
1022
1025
  })
1023
1026
  @Column({
1024
1027
  nullable: true,
@@ -1064,7 +1067,7 @@ export default class StatusPage extends BaseModel {
1064
1067
  type: TableColumnType.JavaScript,
1065
1068
  title: "JavaScript",
1066
1069
  description:
1067
- "Status Page Custom JavaScript. This runs when the status page is loaded.",
1070
+ "Status Page Custom JavaScript. This runs when the status page is loaded from a verified custom domain.",
1068
1071
  })
1069
1072
  @Column({
1070
1073
  nullable: true,
@@ -117,6 +117,7 @@ import StatusPageSubscriberNotificationTemplate from "../../Models/DatabaseModel
117
117
  import StatusPageSubscriberNotificationTemplateService, {
118
118
  Service as StatusPageSubscriberNotificationTemplateServiceClass,
119
119
  } from "../Services/StatusPageSubscriberNotificationTemplateService";
120
+ import { canServeStatusPageCustomizations } from "../Utils/StatusPageCustomizationAccess";
120
121
 
121
122
  type EscapeXmlFunction = (text: string) => string;
122
123
 
@@ -860,6 +861,12 @@ export default class StatusPageAPI extends BaseAPI<
860
861
  ObjectID.validateUUID(statusPageIdParam);
861
862
  const objectId: ObjectID = new ObjectID(statusPageIdParam);
862
863
 
864
+ const allowStatusPageCustomizations: boolean =
865
+ await canServeStatusPageCustomizations({
866
+ req,
867
+ statusPageId: objectId,
868
+ });
869
+
863
870
  const select: Select<StatusPage> = {
864
871
  _id: true,
865
872
  slug: true,
@@ -870,11 +877,7 @@ export default class StatusPageAPI extends BaseAPI<
870
877
  pageTitle: true,
871
878
  pageDescription: true,
872
879
  copyrightText: true,
873
- customCSS: true,
874
- customJavaScript: true,
875
880
  hidePoweredByOneUptimeBranding: true,
876
- headerHTML: true,
877
- footerHTML: true,
878
881
  enableEmailSubscribers: true,
879
882
  enableSlackSubscribers: true,
880
883
  enableMicrosoftTeamsSubscribers: true,
@@ -911,6 +914,13 @@ export default class StatusPageAPI extends BaseAPI<
911
914
  enabledLanguages: true,
912
915
  };
913
916
 
917
+ if (allowStatusPageCustomizations) {
918
+ select.customCSS = true;
919
+ select.customJavaScript = true;
920
+ select.headerHTML = true;
921
+ select.footerHTML = true;
922
+ }
923
+
914
924
  const hasEnabledSSO: PositiveNumber =
915
925
  await StatusPageSsoService.countBy({
916
926
  query: {
@@ -934,6 +944,20 @@ export default class StatusPageAPI extends BaseAPI<
934
944
  throw new BadDataException("Status Page not found");
935
945
  }
936
946
 
947
+ if (!allowStatusPageCustomizations) {
948
+ /*
949
+ * The conditional select above keeps these values out of the
950
+ * normal database result. Explicit redaction is the second half
951
+ * of the boundary: it prevents a future eager-load, hook, cache,
952
+ * or over-populated service result from serializing tenant code
953
+ * onto the authenticated application's origin.
954
+ */
955
+ delete item.customCSS;
956
+ delete item.customJavaScript;
957
+ delete item.headerHTML;
958
+ delete item.footerHTML;
959
+ }
960
+
937
961
  const footerLinks: Array<StatusPageFooterLink> =
938
962
  await StatusPageFooterLinkService.findBy({
939
963
  query: {
@@ -987,6 +1011,7 @@ export default class StatusPageAPI extends BaseAPI<
987
1011
  StatusPageHeaderLink,
988
1012
  ),
989
1013
  hasEnabledSSO: hasEnabledSSO.toNumber(),
1014
+ allowStatusPageCustomizations,
990
1015
  };
991
1016
 
992
1017
  return Response.sendJsonObjectResponse(req, res, response);
@@ -368,6 +368,13 @@ export const SubscriptionPlans: Array<SubscriptionPlan> =
368
368
  export const AnalyticsKey: string = process.env["ANALYTICS_KEY"] || "";
369
369
  export const AnalyticsHost: string = process.env["ANALYTICS_HOST"] || "";
370
370
 
371
+ /*
372
+ * Shared secret Cal.com signs booking webhooks with (App/API/CalWebhook.ts).
373
+ * Server-only — must never be added to FRONTEND_ENV_ALLOW_LIST. Empty means
374
+ * the webhook endpoint is disabled and answers 503.
375
+ */
376
+ export const CalWebhookSecret: string = process.env["CAL_WEBHOOK_SECRET"] || "";
377
+
371
378
  /*
372
379
  * Google Ads offline conversion uploads (MarketingConversions worker job).
373
380
  * Server-only secrets — must never be added to FRONTEND_ENV_ALLOW_LIST.
@@ -0,0 +1,19 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class AddScopeToNetworkDeviceLinkRule1787800000000
4
+ implements MigrationInterface
5
+ {
6
+ public name: string = "AddScopeToNetworkDeviceLinkRule1787800000000";
7
+
8
+ public async up(queryRunner: QueryRunner): Promise<void> {
9
+ await queryRunner.query(
10
+ `ALTER TABLE "NetworkDeviceLinkRule" ADD "scope" character varying(100) DEFAULT 'Project'`,
11
+ );
12
+ }
13
+
14
+ public async down(queryRunner: QueryRunner): Promise<void> {
15
+ await queryRunner.query(
16
+ `ALTER TABLE "NetworkDeviceLinkRule" DROP COLUMN "scope"`,
17
+ );
18
+ }
19
+ }
@@ -0,0 +1,40 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ /*
4
+ * Adds the opt-in that decides whether a Global SSO / Global OIDC provider's
5
+ * project attachments act as an ACCESS boundary as well as a provisioning one.
6
+ *
7
+ * Defaults to false, which is the behaviour every existing installation
8
+ * already has: a global login satisfies SSO enforcement for every project the
9
+ * user is a member of. Attachments were introduced as the provisioning
10
+ * allow-list - "a SAML assertion can only ever provision a user into projects
11
+ * that a master admin has explicitly attached here" - and the login routers
12
+ * grant a session on membership of ANY project, so silently reinterpreting
13
+ * them as an access boundary would lock existing users out of projects they
14
+ * legitimately reach, with nothing in the product to recover with.
15
+ *
16
+ * An admin who wants the narrower behaviour turns it on per provider.
17
+ */
18
+ export class AddRestrictToAttachedProjectsToGlobalSso1787900000000
19
+ implements MigrationInterface
20
+ {
21
+ public name = "AddRestrictToAttachedProjectsToGlobalSso1787900000000";
22
+
23
+ public async up(queryRunner: QueryRunner): Promise<void> {
24
+ await queryRunner.query(
25
+ `ALTER TABLE "GlobalSSO" ADD "restrictToAttachedProjects" boolean NOT NULL DEFAULT false`,
26
+ );
27
+ await queryRunner.query(
28
+ `ALTER TABLE "GlobalOIDC" ADD "restrictToAttachedProjects" boolean NOT NULL DEFAULT false`,
29
+ );
30
+ }
31
+
32
+ public async down(queryRunner: QueryRunner): Promise<void> {
33
+ await queryRunner.query(
34
+ `ALTER TABLE "GlobalOIDC" DROP COLUMN "restrictToAttachedProjects"`,
35
+ );
36
+ await queryRunner.query(
37
+ `ALTER TABLE "GlobalSSO" DROP COLUMN "restrictToAttachedProjects"`,
38
+ );
39
+ }
40
+ }
@@ -531,6 +531,8 @@ import { AddDeviceRoleAndDeclaredLinkParent1787400000000 } from "./1787400000000
531
531
  import { AddEnableSearchEngineIndexingToStatusPage1787500000000 } from "./1787500000000-AddEnableSearchEngineIndexingToStatusPage";
532
532
  import { AddNetworkDeviceReachabilityColumns1787600000000 } from "./1787600000000-AddNetworkDeviceReachabilityColumns";
533
533
  import { FixTotpOtpUrlAlgorithm1787700000000 } from "./1787700000000-FixTotpOtpUrlAlgorithm";
534
+ import { AddScopeToNetworkDeviceLinkRule1787800000000 } from "./1787800000000-AddScopeToNetworkDeviceLinkRule";
535
+ import { AddRestrictToAttachedProjectsToGlobalSso1787900000000 } from "./1787900000000-AddRestrictToAttachedProjectsToGlobalSso";
534
536
  import { MigrationName1787142779538 } from "./1787142779538-MigrationName";
535
537
  import { MigrationName1787156982416 } from "./1787156982416-MigrationName";
536
538
 
@@ -1070,4 +1072,6 @@ export default [
1070
1072
  AddEnableSearchEngineIndexingToStatusPage1787500000000,
1071
1073
  AddNetworkDeviceReachabilityColumns1787600000000,
1072
1074
  FixTotpOtpUrlAlgorithm1787700000000,
1075
+ AddScopeToNetworkDeviceLinkRule1787800000000,
1076
+ AddRestrictToAttachedProjectsToGlobalSso1787900000000,
1073
1077
  ];
@@ -10,6 +10,18 @@ import crypto from "crypto";
10
10
  import logger, { getLogAttributesFromRequest } from "../Utils/Logger";
11
11
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
12
12
 
13
+ /*
14
+ * Slack sends the digest as "v0=" followed by the 64 hex characters of a
15
+ * SHA-256 HMAC. Same reasoning as WhatsAppAuthorization: the header is
16
+ * attacker-controlled on an endpoint whose only authentication IS the
17
+ * signature, and crypto.timingSafeEqual throws RangeError on a length
18
+ * mismatch instead of returning false, so the shape is validated before the
19
+ * comparison. A missing header is covered by the same check - it used to
20
+ * reach Buffer.from(undefined) and throw a TypeError.
21
+ */
22
+ const SIGNATURE_PREFIX: string = "v0=";
23
+ const HEX_DIGEST_REGEX: RegExp = /^[a-f0-9]{64}$/i;
24
+
13
25
  export default class SlackAuthorization {
14
26
  @CaptureSpan()
15
27
  public static async isAuthorizedSlackRequest(
@@ -39,7 +51,9 @@ export default class SlackAuthorization {
39
51
  // validate slack signing secret
40
52
  const slackSigningSecret: string = SlackAppSigningSecret.toString();
41
53
 
42
- const slackSignature: string = req.headers["x-slack-signature"] as string;
54
+ const slackSignature: string | undefined = req.headers[
55
+ "x-slack-signature"
56
+ ] as string | undefined;
43
57
  const timestamp: string = req.headers[
44
58
  "x-slack-request-timestamp"
45
59
  ] as string;
@@ -57,19 +71,42 @@ export default class SlackAuthorization {
57
71
  logger.debug(`requestBody: `, getLogAttributesFromRequest(req));
58
72
  logger.debug(requestBody, getLogAttributesFromRequest(req));
59
73
 
74
+ const providedDigest: string =
75
+ slackSignature && slackSignature.startsWith(SIGNATURE_PREFIX)
76
+ ? slackSignature.slice(SIGNATURE_PREFIX.length)
77
+ : "";
78
+
79
+ if (!HEX_DIGEST_REGEX.test(providedDigest)) {
80
+ logger.error(
81
+ "Slack request has a missing or malformed X-Slack-Signature header.",
82
+ getLogAttributesFromRequest(req),
83
+ );
84
+ return Response.sendErrorResponse(
85
+ req,
86
+ res,
87
+ new BadDataException("Slack Signature Verification Failed."),
88
+ );
89
+ }
90
+
60
91
  const baseString: string = `v0:${timestamp}:${requestBody}`;
61
- const signature: string = `v0=${crypto.createHmac("sha256", slackSigningSecret).update(baseString).digest("hex")}`;
92
+ const expectedDigest: string = crypto
93
+ .createHmac("sha256", slackSigningSecret)
94
+ .update(baseString)
95
+ .digest("hex");
62
96
 
63
97
  logger.debug(
64
- `Generated signature: ${signature}`,
98
+ `Generated signature: ${SIGNATURE_PREFIX}${expectedDigest}`,
65
99
  getLogAttributesFromRequest(req),
66
100
  );
67
101
 
68
- // check if the signature is valid
102
+ /*
103
+ * Both sides are decoded from 64 validated hex characters, so both
104
+ * buffers are 32 bytes and timingSafeEqual cannot throw here.
105
+ */
69
106
  if (
70
107
  !crypto.timingSafeEqual(
71
- Buffer.from(signature) as Uint8Array,
72
- Buffer.from(slackSignature) as Uint8Array,
108
+ Buffer.from(expectedDigest, "hex") as Uint8Array,
109
+ Buffer.from(providedDigest, "hex") as Uint8Array,
73
110
  )
74
111
  ) {
75
112
  logger.error(