@oneuptime/common 12.0.17 → 12.0.19

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 (220) hide show
  1. package/Models/DatabaseModels/EnterpriseLicense.ts +23 -0
  2. package/Models/DatabaseModels/Index.ts +4 -2
  3. package/Models/DatabaseModels/Monitor.ts +39 -9
  4. package/Models/DatabaseModels/UserMicrosoftTeams.ts +344 -0
  5. package/Models/DatabaseModels/UserNotificationRule.ts +144 -8
  6. package/Models/DatabaseModels/UserNotificationSetting.ts +34 -0
  7. package/Models/DatabaseModels/UserOnCallLogTimeline.ts +95 -0
  8. package/Models/DatabaseModels/UserSlack.ts +341 -0
  9. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +9 -0
  10. package/Server/API/UserMicrosoftTeamsAPI.ts +127 -0
  11. package/Server/API/UserSlackAPI.ts +125 -0
  12. package/Server/EnvironmentConfig.ts +12 -107
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.ts +33 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.ts +51 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.ts +117 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.ts +197 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  18. package/Server/Infrastructure/Queue.ts +7 -0
  19. package/Server/Services/BillingService.ts +8 -0
  20. package/Server/Services/EnterpriseLicenseService.ts +60 -0
  21. package/Server/Services/Index.ts +6 -2
  22. package/Server/Services/LlmLogService.ts +25 -9
  23. package/Server/Services/OnCallReadinessService.ts +86 -2
  24. package/Server/Services/ProjectService.ts +67 -0
  25. package/Server/Services/UserMicrosoftTeamsService.ts +208 -0
  26. package/Server/Services/UserNotificationMethodAdminService.ts +167 -5
  27. package/Server/Services/UserNotificationRuleAdminService.ts +40 -6
  28. package/Server/Services/UserNotificationRuleService.ts +589 -11
  29. package/Server/Services/UserNotificationSettingService.ts +138 -0
  30. package/Server/Services/UserService.ts +28 -0
  31. package/Server/Services/UserSlackService.ts +218 -0
  32. package/Server/Services/WorkspaceProjectAuthTokenService.ts +67 -1
  33. package/Server/Services/WorkspaceUserAuthTokenService.ts +73 -0
  34. package/Server/Services/WorkspaceUserNotificationService.ts +190 -0
  35. package/Server/Utils/Marketing/MarketingEventUtil.ts +145 -0
  36. package/Server/Utils/Marketing/MarketingEventWebhook.ts +114 -0
  37. package/Server/Utils/Monitor/MonitorLogUtil.ts +14 -1
  38. package/Server/Utils/Monitor/MonitorPayloadRedaction.ts +321 -0
  39. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +191 -4
  40. package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +22 -14
  41. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +22 -11
  42. package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +37 -1
  43. package/Tests/App/Dashboard/OnCallRulesTable.test.tsx +18 -10
  44. package/Tests/App/Dashboard/PayAsYouGoNotices.test.tsx +17 -0
  45. package/Tests/Models/DatabaseModels/DomainRoleTierCoverage.test.ts +27 -1
  46. package/Tests/Models/DatabaseModels/MonitorSecretKeyColumnAccessControl.test.ts +214 -0
  47. package/Tests/Server/API/OnCallReadinessAPI.test.ts +145 -26
  48. package/Tests/Server/API/UserNotificationMethodAdminAPI.test.ts +2 -2
  49. package/Tests/Server/Services/AIServiceDailyBudget.test.ts +11 -1
  50. package/Tests/Server/Services/AdminRuleEditGuards.test.ts +22 -8
  51. package/Tests/Server/Services/BillingService.test.ts +20 -0
  52. package/Tests/Server/Services/DeliverNotificationForRuleExtraction.test.ts +18 -2
  53. package/Tests/Server/Services/EpisodeRuleSeverityRepair.test.ts +5 -1
  54. package/Tests/Server/Services/LlmLogServiceTokenAggregateParams.test.ts +640 -0
  55. package/Tests/Server/Services/NotificationChannelEventCoverage.test.ts +246 -22
  56. package/Tests/Server/Services/NotificationDeletionImpact.test.ts +136 -8
  57. package/Tests/Server/Services/OnCallNotificationFallback.test.ts +191 -8
  58. package/Tests/Server/Services/OnCallReadinessService.test.ts +343 -18
  59. package/Tests/Server/Services/SeverityRuleBackfill.test.ts +5 -1
  60. package/Tests/Server/Services/UserMicrosoftTeamsService.test.ts +343 -0
  61. package/Tests/Server/Services/UserNotificationMethodAdminService.test.ts +209 -8
  62. package/Tests/Server/Services/UserNotificationRuleAdminGuards.test.ts +22 -6
  63. package/Tests/Server/Services/UserNotificationRuleDefaultCreation.test.ts +12 -6
  64. package/Tests/Server/Services/UserNotificationRuleExecuteItem.test.ts +37 -3
  65. package/Tests/Server/Services/UserNotificationRuleWorkspaceDelivery.test.ts +669 -0
  66. package/Tests/Server/Services/UserNotificationSettingWorkspaceChannels.test.ts +410 -0
  67. package/Tests/Server/Services/UserSlackService.test.ts +407 -0
  68. package/Tests/Server/Services/WorkspaceProjectAuthTokenDisconnectCascade.test.ts +224 -0
  69. package/Tests/Server/Services/WorkspaceUserAuthTokenNotificationMethodCascade.test.ts +189 -0
  70. package/Tests/Server/Services/WorkspaceUserNotificationService.test.ts +496 -0
  71. package/Tests/Server/Types/Database/Permissions/AdminNotificationRuleAccess.test.ts +30 -17
  72. package/Tests/Server/Types/Database/Permissions/CreateOwnershipScoping.test.ts +8 -3
  73. package/Tests/Server/Types/Database/Permissions/OwnerOnlyColumns.test.ts +72 -3
  74. package/Tests/Server/Types/Database/Permissions/UserNotificationRuleScoping.test.ts +22 -18
  75. package/Tests/Server/Types/Database/Permissions/WorkspaceMethodStampedColumnCreate.test.ts +178 -0
  76. package/Tests/Server/Utils/Marketing/MarketingEventUtil.test.ts +259 -0
  77. package/Tests/Server/Utils/Marketing/MarketingEventWebhook.test.ts +257 -0
  78. package/Tests/Server/Utils/Monitor/MonitorLogUtilRedaction.test.ts +300 -0
  79. package/Tests/Server/Utils/Monitor/MonitorPayloadRedaction.test.ts +381 -0
  80. package/Tests/Server/Utils/Runbook/RunbookExecutePermission.test.ts +228 -0
  81. package/Tests/Server/Utils/SessionReplay/SessionReplayErasureTombstone.test.ts +189 -0
  82. package/Tests/Server/Utils/SessionReplay/SessionReplayUsage.test.ts +205 -0
  83. package/Tests/Server/Utils/Telemetry/AppMetrics.test.ts +275 -0
  84. package/Tests/Server/Utils/Telemetry/TelemetryContext.test.ts +279 -0
  85. package/Tests/Server/Utils/Workspace/MicrosoftTeamsDirectMessage.test.ts +454 -0
  86. package/Tests/Types/Kubernetes/KubernetesObjectParsers.test.ts +794 -0
  87. package/Tests/Types/Kubernetes/KubernetesRightSizing.test.ts +53 -0
  88. package/Tests/Types/Measurement/MeasurementAggregationType.test.ts +100 -0
  89. package/Tests/Types/Monitor/MonitorStepConfigHelpers.test.ts +114 -0
  90. package/Tests/Types/Workspace/WorkspaceType.test.ts +53 -0
  91. package/Tests/UI/Utils/NotificationMethodUtil.test.ts +137 -9
  92. package/Tests/UI/Utils/PermissionGate.test.ts +105 -0
  93. package/Tests/Utils/Dashboard/Components/DashboardComponentDefaults.test.ts +257 -0
  94. package/Types/Billing/PayAsYouGoPricing.ts +1 -1
  95. package/Types/Marketing/MarketingEvent.ts +97 -0
  96. package/UI/Utils/NotificationMethodUtil.ts +41 -1
  97. package/UI/Utils/PermissionGate.ts +56 -0
  98. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js +24 -0
  99. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js.map +1 -1
  100. package/build/dist/Models/DatabaseModels/Index.js +4 -2
  101. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  102. package/build/dist/Models/DatabaseModels/Monitor.js +39 -9
  103. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  104. package/build/dist/Models/DatabaseModels/UserMicrosoftTeams.js +363 -0
  105. package/build/dist/Models/DatabaseModels/UserMicrosoftTeams.js.map +1 -0
  106. package/build/dist/Models/DatabaseModels/UserNotificationRule.js +145 -8
  107. package/build/dist/Models/DatabaseModels/UserNotificationRule.js.map +1 -1
  108. package/build/dist/Models/DatabaseModels/UserNotificationSetting.js +38 -0
  109. package/build/dist/Models/DatabaseModels/UserNotificationSetting.js.map +1 -1
  110. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +96 -0
  111. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
  112. package/build/dist/Models/DatabaseModels/UserSlack.js +361 -0
  113. package/build/dist/Models/DatabaseModels/UserSlack.js.map +1 -0
  114. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  115. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js +82 -0
  116. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js.map +1 -0
  117. package/build/dist/Server/API/UserSlackAPI.js +81 -0
  118. package/build/dist/Server/API/UserSlackAPI.js.map +1 -0
  119. package/build/dist/Server/EnvironmentConfig.js +10 -70
  120. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  121. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.js +26 -0
  122. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.js.map +1 -0
  123. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.js +37 -0
  124. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.js.map +1 -0
  125. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js +106 -0
  126. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js.map +1 -0
  127. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js +78 -0
  128. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js.map +1 -0
  129. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  130. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  131. package/build/dist/Server/Infrastructure/Queue.js +7 -0
  132. package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
  133. package/build/dist/Server/Services/BillingService.js +6 -0
  134. package/build/dist/Server/Services/BillingService.js.map +1 -1
  135. package/build/dist/Server/Services/EnterpriseLicenseService.js +65 -0
  136. package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
  137. package/build/dist/Server/Services/Index.js +6 -2
  138. package/build/dist/Server/Services/Index.js.map +1 -1
  139. package/build/dist/Server/Services/LlmLogService.js +25 -9
  140. package/build/dist/Server/Services/LlmLogService.js.map +1 -1
  141. package/build/dist/Server/Services/OnCallReadinessService.js +74 -2
  142. package/build/dist/Server/Services/OnCallReadinessService.js.map +1 -1
  143. package/build/dist/Server/Services/ProjectService.js +56 -0
  144. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  145. package/build/dist/Server/Services/UserMicrosoftTeamsService.js +183 -0
  146. package/build/dist/Server/Services/UserMicrosoftTeamsService.js.map +1 -0
  147. package/build/dist/Server/Services/UserNotificationMethodAdminService.js +138 -2
  148. package/build/dist/Server/Services/UserNotificationMethodAdminService.js.map +1 -1
  149. package/build/dist/Server/Services/UserNotificationRuleAdminService.js +32 -4
  150. package/build/dist/Server/Services/UserNotificationRuleAdminService.js.map +1 -1
  151. package/build/dist/Server/Services/UserNotificationRuleService.js +499 -67
  152. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  153. package/build/dist/Server/Services/UserNotificationSettingService.js +118 -0
  154. package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
  155. package/build/dist/Server/Services/UserService.js +26 -1
  156. package/build/dist/Server/Services/UserService.js.map +1 -1
  157. package/build/dist/Server/Services/UserSlackService.js +195 -0
  158. package/build/dist/Server/Services/UserSlackService.js.map +1 -0
  159. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js +64 -1
  160. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js.map +1 -1
  161. package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js +70 -0
  162. package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js.map +1 -1
  163. package/build/dist/Server/Services/WorkspaceUserNotificationService.js +129 -0
  164. package/build/dist/Server/Services/WorkspaceUserNotificationService.js.map +1 -0
  165. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js +94 -0
  166. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js.map +1 -0
  167. package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js +93 -0
  168. package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js.map +1 -0
  169. package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js +12 -1
  170. package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js.map +1 -1
  171. package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js +233 -0
  172. package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js.map +1 -0
  173. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +141 -4
  174. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  175. package/build/dist/Types/Billing/PayAsYouGoPricing.js +1 -1
  176. package/build/dist/Types/Billing/PayAsYouGoPricing.js.map +1 -1
  177. package/build/dist/Types/Marketing/MarketingEvent.js +43 -0
  178. package/build/dist/Types/Marketing/MarketingEvent.js.map +1 -0
  179. package/build/dist/UI/Utils/NotificationMethodUtil.js +33 -1
  180. package/build/dist/UI/Utils/NotificationMethodUtil.js.map +1 -1
  181. package/build/dist/UI/Utils/PermissionGate.js +32 -0
  182. package/build/dist/UI/Utils/PermissionGate.js.map +1 -1
  183. package/package.json +1 -1
  184. package/Models/DatabaseModels/MarketingConversion.ts +0 -410
  185. package/Server/Services/MarketingConversionService.ts +0 -10
  186. package/Server/Utils/Marketing/ConversionUploadProvider.ts +0 -215
  187. package/Server/Utils/Marketing/ConversionUploadProviders.ts +0 -22
  188. package/Server/Utils/Marketing/Providers/GoogleAds.ts +0 -333
  189. package/Server/Utils/Marketing/Providers/LinkedIn.ts +0 -185
  190. package/Server/Utils/Marketing/Providers/Meta.ts +0 -182
  191. package/Server/Utils/Marketing/Providers/MicrosoftAds.ts +0 -223
  192. package/Server/Utils/Marketing/Providers/Reddit.ts +0 -208
  193. package/Tests/Server/Utils/Marketing/AdUploadableConversionTypes.test.ts +0 -315
  194. package/Tests/Server/Utils/Marketing/ConversionUploadProvider.test.ts +0 -300
  195. package/Tests/Server/Utils/Marketing/GoogleAds.test.ts +0 -592
  196. package/Tests/Server/Utils/Marketing/LinkedIn.test.ts +0 -282
  197. package/Tests/Server/Utils/Marketing/Meta.test.ts +0 -304
  198. package/Tests/Server/Utils/Marketing/MicrosoftAds.test.ts +0 -263
  199. package/Tests/Server/Utils/Marketing/Reddit.test.ts +0 -259
  200. package/Types/Marketing/MarketingConversion.ts +0 -53
  201. package/build/dist/Models/DatabaseModels/MarketingConversion.js +0 -445
  202. package/build/dist/Models/DatabaseModels/MarketingConversion.js.map +0 -1
  203. package/build/dist/Server/Services/MarketingConversionService.js +0 -9
  204. package/build/dist/Server/Services/MarketingConversionService.js.map +0 -1
  205. package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js +0 -93
  206. package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js.map +0 -1
  207. package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js +0 -20
  208. package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js.map +0 -1
  209. package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js +0 -221
  210. package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js.map +0 -1
  211. package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js +0 -137
  212. package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js.map +0 -1
  213. package/build/dist/Server/Utils/Marketing/Providers/Meta.js +0 -133
  214. package/build/dist/Server/Utils/Marketing/Providers/Meta.js.map +0 -1
  215. package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js +0 -143
  216. package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js.map +0 -1
  217. package/build/dist/Server/Utils/Marketing/Providers/Reddit.js +0 -145
  218. package/build/dist/Server/Utils/Marketing/Providers/Reddit.js.map +0 -1
  219. package/build/dist/Types/Marketing/MarketingConversion.js +0 -52
  220. package/build/dist/Types/Marketing/MarketingConversion.js.map +0 -1
@@ -1,7 +1,9 @@
1
1
  import UserCall from "../../../../../Models/DatabaseModels/UserCall";
2
2
  import UserEmail from "../../../../../Models/DatabaseModels/UserEmail";
3
+ import UserMicrosoftTeams from "../../../../../Models/DatabaseModels/UserMicrosoftTeams";
3
4
  import UserNotificationRule from "../../../../../Models/DatabaseModels/UserNotificationRule";
4
5
  import UserPush from "../../../../../Models/DatabaseModels/UserPush";
6
+ import UserSlack from "../../../../../Models/DatabaseModels/UserSlack";
5
7
  import UserSMS from "../../../../../Models/DatabaseModels/UserSMS";
6
8
  import UserTelegram from "../../../../../Models/DatabaseModels/UserTelegram";
7
9
  import UserWebhook from "../../../../../Models/DatabaseModels/UserWebhook";
@@ -38,12 +40,13 @@ import { describe, expect, test } from "@jest/globals";
38
40
  * decide it: a member with no matching rule was silently unpageable, and the
39
41
  * only person able to notice was the person who was not being notified.
40
42
  *
41
- * PHASE 3 SHIPPED THAT CAPABILITY ON THE RULE TABLE AND ONLY THERE. The seven
43
+ * PHASE 3 SHIPPED THAT CAPABILITY ON THE RULE TABLE AND ONLY THERE. The nine
42
44
  * notification-method models - UserEmail, UserSMS, UserCall, UserPush,
43
- * UserWhatsApp, UserTelegram, UserWebhook - stay CurrentUser-only on every
44
- * verb, so an administrator reading one of THEM is refused exactly like any
45
- * other member. That split is not a leftover; it is the design, and it is the
46
- * design because the alternative was tried and did not hold.
45
+ * UserWhatsApp, UserTelegram, UserSlack, UserMicrosoftTeams, UserWebhook -
46
+ * stay CurrentUser-only on every verb, so an administrator reading one of THEM
47
+ * is refused exactly like any other member. That split is not a leftover; it
48
+ * is the design, and it is the design because the alternative was tried and
49
+ * did not hold.
47
50
  *
48
51
  * WHY THE METHOD TABLES COULD NOT BE WIDENED. Widening a read on a model that
49
52
  * scopes rows by owner is a privilege change rather than a convenience change,
@@ -77,7 +80,7 @@ import { describe, expect, test } from "@jest/globals";
77
80
  *
78
81
  * - the rule table lifts for an administrator and not for a plain member, on
79
82
  * all four verbs;
80
- * - the seven method models lift for NOBODY on ANY verb, and the read case is
83
+ * - the nine method models lift for NOBODY on ANY verb, and the read case is
81
84
  * asserted in both directions because it is the one that shipped widened
82
85
  * once already;
83
86
  * - the rule's method FK id columns are admin-readable while the method
@@ -147,9 +150,9 @@ const NEW_GRANULAR_PERMISSIONS: Array<Permission> = [
147
150
  ];
148
151
 
149
152
  /*
150
- * The seven notification methods a rule can point at. Every method assertion
151
- * runs against all seven rather than against a representative one: they are
152
- * seven copies of one decorator shape, and a widening applied to one of them is
153
+ * The nine notification methods a rule can point at. Every method assertion
154
+ * runs against all nine rather than against a representative one: they are
155
+ * nine copies of one decorator shape, and a widening applied to one of them is
153
156
  * exactly the kind of thing that survives review.
154
157
  */
155
158
  const METHOD_MODELS: Array<[string, ModelConstructor]> = [
@@ -159,6 +162,8 @@ const METHOD_MODELS: Array<[string, ModelConstructor]> = [
159
162
  ["UserPush", UserPush],
160
163
  ["UserWhatsApp", UserWhatsApp],
161
164
  ["UserTelegram", UserTelegram],
165
+ ["UserSlack", UserSlack],
166
+ ["UserMicrosoftTeams", UserMicrosoftTeams],
162
167
  ["UserWebhook", UserWebhook],
163
168
  ];
164
169
 
@@ -478,10 +483,10 @@ describe("the rule table - an administrator's row scope lifts, a member's does n
478
483
  });
479
484
  });
480
485
 
481
- describe("the seven methods - an administrator may not look either", () => {
486
+ describe("the nine methods - an administrator may not look either", () => {
482
487
  /*
483
488
  * THE CORE DECISION OF THIS PHASE AS IT FINALLY SHIPPED, AND THE ONE MOST
484
- * LIKELY TO BE "TIDIED UP" LATER, because seven models that differ from their
489
+ * LIKELY TO BE "TIDIED UP" LATER, because nine models that differ from their
485
490
  * sibling in all four lists look like an oversight rather than a decision.
486
491
  *
487
492
  * The read half of these lists was widened once and then reverted. The reason
@@ -1243,7 +1248,7 @@ describe("the credentials behind a notification method, guarded by the table sco
1243
1248
  * column-level decision to verify per column and per query shape, because
1244
1249
  * there is no query on these models that reaches another member's ROW - so
1245
1250
  * every column on that row, credential or label, is out of reach for the same
1246
- * single reason. The five columns are still enumerated rather than reduced to
1251
+ * single reason. The columns are still enumerated rather than reduced to
1247
1252
  * one representative, because they are what a reader comes here looking for
1248
1253
  * and because "the row is unreachable" is a claim worth checking against the
1249
1254
  * things that would actually matter if it were false.
@@ -1251,8 +1256,10 @@ describe("the credentials behind a notification method, guarded by the table sco
1251
1256
  * The columns: the webhook URL and its signing secret (both bearer
1252
1257
  * credentials - anyone holding them can impersonate OneUptime to the member's
1253
1258
  * endpoint), the push device token, the Telegram chat id an unverified bot
1254
- * message can be addressed to, and the verification code that turns
1255
- * possession of that chat id into a verified method.
1259
+ * message can be addressed to, the verification code that turns possession
1260
+ * of that chat id into a verified method, and the Slack member id and
1261
+ * Microsoft Teams user id the project's workspace bots deliver direct
1262
+ * messages to.
1256
1263
  */
1257
1264
 
1258
1265
  /*
@@ -1266,6 +1273,8 @@ describe("the credentials behind a notification method, guarded by the table sco
1266
1273
  ["UserPush", "deviceToken", UserPush],
1267
1274
  ["UserTelegram", "telegramChatId", UserTelegram],
1268
1275
  ["UserTelegram", "verificationCode", UserTelegram],
1276
+ ["UserSlack", "slackUserId", UserSlack],
1277
+ ["UserMicrosoftTeams", "microsoftTeamsUserId", UserMicrosoftTeams],
1269
1278
  ];
1270
1279
 
1271
1280
  test.each(CREDENTIAL_COLUMNS)(
@@ -1375,8 +1384,8 @@ describe("the rule relation - an administrator reads the FK, never the method be
1375
1384
  *
1376
1385
  * Neither check can see the query, so neither can tell whose row this is.
1377
1386
  * That leaves the outer column list as the only place the decision can be
1378
- * made, and it is why the seven method RELATION columns on
1379
- * UserNotificationRule are `read: [Permission.CurrentUser]` while the seven
1387
+ * made, and it is why the nine method RELATION columns on
1388
+ * UserNotificationRule are `read: [Permission.CurrentUser]` while the nine
1380
1389
  * FK ID columns beside them are admin-readable. The pairing is the design in
1381
1390
  * one line: an administrator may learn WHICH method a rule points at, and may
1382
1391
  * re-point it at a different one, without ever learning what the method IS.
@@ -1397,6 +1406,8 @@ describe("the rule relation - an administrator reads the FK, never the method be
1397
1406
  "userPushId",
1398
1407
  "userWhatsAppId",
1399
1408
  "userTelegramId",
1409
+ "userSlackId",
1410
+ "userMicrosoftTeamsId",
1400
1411
  "userWebhookId",
1401
1412
  ];
1402
1413
 
@@ -1412,6 +1423,8 @@ describe("the rule relation - an administrator reads the FK, never the method be
1412
1423
  ["userPush", "deviceToken"],
1413
1424
  ["userWhatsApp", "phone"],
1414
1425
  ["userTelegram", "telegramChatId"],
1426
+ ["userSlack", "slackUserId"],
1427
+ ["userMicrosoftTeams", "microsoftTeamsUserId"],
1415
1428
  ["userWebhook", "webhookUrl"],
1416
1429
  ];
1417
1430
 
@@ -1420,7 +1433,7 @@ describe("the rule relation - an administrator reads the FK, never the method be
1420
1433
  async (grant: Permission): Promise<void> => {
1421
1434
  /*
1422
1435
  * The capability half, and it has to pass or the refusal below has taken
1423
- * the feature with it. Selecting all seven ids at once rather than one
1436
+ * the feature with it. Selecting all nine ids at once rather than one
1424
1437
  * representative, because the admin surface renders the whole row and a
1425
1438
  * single narrowed column would break the page while six tests still
1426
1439
  * passed.
@@ -4,12 +4,14 @@ import User from "../../../../../Models/DatabaseModels/User";
4
4
  import UserCall from "../../../../../Models/DatabaseModels/UserCall";
5
5
  import UserEmail from "../../../../../Models/DatabaseModels/UserEmail";
6
6
  import UserIncomingCallNumber from "../../../../../Models/DatabaseModels/UserIncomingCallNumber";
7
+ import UserMicrosoftTeams from "../../../../../Models/DatabaseModels/UserMicrosoftTeams";
7
8
  import UserNotificationRule from "../../../../../Models/DatabaseModels/UserNotificationRule";
8
9
  import UserNotificationSetting from "../../../../../Models/DatabaseModels/UserNotificationSetting";
9
10
  import UserOnCallLog from "../../../../../Models/DatabaseModels/UserOnCallLog";
10
11
  import UserPush from "../../../../../Models/DatabaseModels/UserPush";
11
12
  import UserSMS from "../../../../../Models/DatabaseModels/UserSMS";
12
13
  import UserSession from "../../../../../Models/DatabaseModels/UserSession";
14
+ import UserSlack from "../../../../../Models/DatabaseModels/UserSlack";
13
15
  import UserTelegram from "../../../../../Models/DatabaseModels/UserTelegram";
14
16
  import UserTotpAuth from "../../../../../Models/DatabaseModels/UserTotpAuth";
15
17
  import UserWebAuthn from "../../../../../Models/DatabaseModels/UserWebAuthn";
@@ -42,8 +44,8 @@ import { describe, expect, test } from "@jest/globals";
42
44
  * allow-list intersection over column names. So on any model whose create list
43
45
  * is CurrentUser-only, a caller could write a row owned by somebody else.
44
46
  *
45
- * Fourteen models have that shape, and every one of them is a "my own X"
46
- * resource: notification rules, all seven notification methods behind them,
47
+ * Sixteen models have that shape, and every one of them is a "my own X"
48
+ * resource: notification rules, all nine notification methods behind them,
47
49
  * sessions, TOTP and WebAuthn credentials, workspace tokens. On the
48
50
  * notification models the ownership column is not bookkeeping — it decides
49
51
  * whose on-call pages select the row, while the address actually messaged is
@@ -124,7 +126,8 @@ type ModelConstructor = { new (): BaseModel };
124
126
  * Every model whose create list is exactly [Permission.CurrentUser]. These are
125
127
  * the ones the fix protects, and the ones an attacker could previously write on
126
128
  * another user's behalf. Enumerated rather than discovered so that adding a
127
- * fifteenth model with this shape is a deliberate act that shows up in review.
129
+ * seventeenth model with this shape is a deliberate act that shows up in
130
+ * review.
128
131
  */
129
132
  const CURRENT_USER_ONLY_MODELS: Array<[string, ModelConstructor]> = [
130
133
  ["UserNotificationRule", UserNotificationRule],
@@ -135,6 +138,8 @@ const CURRENT_USER_ONLY_MODELS: Array<[string, ModelConstructor]> = [
135
138
  ["UserPush", UserPush],
136
139
  ["UserWhatsApp", UserWhatsApp],
137
140
  ["UserTelegram", UserTelegram],
141
+ ["UserSlack", UserSlack],
142
+ ["UserMicrosoftTeams", UserMicrosoftTeams],
138
143
  ["UserNotificationSetting", UserNotificationSetting],
139
144
  ["UserIncomingCallNumber", UserIncomingCallNumber],
140
145
  ["UserSession", UserSession],
@@ -1,6 +1,8 @@
1
1
  import UserCall from "../../../../../Models/DatabaseModels/UserCall";
2
2
  import UserEmail from "../../../../../Models/DatabaseModels/UserEmail";
3
+ import UserMicrosoftTeams from "../../../../../Models/DatabaseModels/UserMicrosoftTeams";
3
4
  import UserPush from "../../../../../Models/DatabaseModels/UserPush";
5
+ import UserSlack from "../../../../../Models/DatabaseModels/UserSlack";
4
6
  import UserSMS from "../../../../../Models/DatabaseModels/UserSMS";
5
7
  import UserTelegram from "../../../../../Models/DatabaseModels/UserTelegram";
6
8
  import UserWebhook from "../../../../../Models/DatabaseModels/UserWebhook";
@@ -34,7 +36,7 @@ import UserType from "../../../../../Types/UserType";
34
36
  * administrators - which is the feature, and is correct - and the moment it
35
37
  * does, an admin's read of that table is no longer confined to their own rows.
36
38
  * Everything reachable from those rows is then reachable by an admin, and the
37
- * seven notification-method models hang off them by ManyToOne relation.
39
+ * nine notification-method models hang off them by ManyToOne relation.
38
40
  *
39
41
  * There are FOUR routes into a marked column, and closing three of them is the
40
42
  * same as closing none. Every route below is driven through the real permission
@@ -49,7 +51,7 @@ import UserType from "../../../../../Types/UserType";
49
51
  * Route 3 sort: { webhookUrl: "ASC" } injected post-gate
50
52
  * Route 4 query: { webhookUrl: "https://..." } as a value oracle
51
53
  *
52
- * HOW THE ADMIN CASE IS SET UP, AND WHY IT IS NOT THEATRE. Today the seven
54
+ * HOW THE ADMIN CASE IS SET UP, AND WHY IT IS NOT THEATRE. Today the nine
53
55
  * method models list [CurrentUser] and nothing else, so TenantPermission stamps
54
56
  * every non-root caller's own userId onto their query and an administrator is
55
57
  * indistinguishable from a plain member - the admin case is currently
@@ -68,7 +70,7 @@ const otherUserId: ObjectID = ObjectID.generate();
68
70
  type ModelTypeUnderTest = { new (): BaseModel };
69
71
 
70
72
  /*
71
- * The seven notification-method models with the post-Phase-3 table read. Only
73
+ * The nine notification-method models with the post-Phase-3 table read. Only
72
74
  * the table access control is restated; every column, its @OwnerOnlyColumn
73
75
  * mark, the tenant column and @CurrentUserCanAccessRecordBy("userId") are
74
76
  * inherited from the production model.
@@ -103,6 +105,12 @@ class AdminReadableUserWhatsApp extends UserWhatsApp {}
103
105
  @TableAccessControl(ADMIN_READABLE_TABLE_ACCESS)
104
106
  class AdminReadableUserTelegram extends UserTelegram {}
105
107
 
108
+ @TableAccessControl(ADMIN_READABLE_TABLE_ACCESS)
109
+ class AdminReadableUserSlack extends UserSlack {}
110
+
111
+ @TableAccessControl(ADMIN_READABLE_TABLE_ACCESS)
112
+ class AdminReadableUserMicrosoftTeams extends UserMicrosoftTeams {}
113
+
106
114
  @TableAccessControl(ADMIN_READABLE_TABLE_ACCESS)
107
115
  class AdminReadableUserWebhook extends UserWebhook {}
108
116
 
@@ -218,6 +226,32 @@ class AdminReadableRuleModel extends BaseModel {
218
226
  })
219
227
  public userTelegram?: UserTelegram = undefined;
220
228
 
229
+ @ColumnAccessControl({
230
+ create: [Permission.CurrentUser],
231
+ read: [Permission.CurrentUser, Permission.ProjectAdmin],
232
+ update: [],
233
+ })
234
+ @TableColumn({
235
+ type: TableColumnType.Entity,
236
+ modelType: UserSlack,
237
+ manyToOneRelationColumn: "userSlackId",
238
+ title: "User Slack",
239
+ })
240
+ public userSlack?: UserSlack = undefined;
241
+
242
+ @ColumnAccessControl({
243
+ create: [Permission.CurrentUser],
244
+ read: [Permission.CurrentUser, Permission.ProjectAdmin],
245
+ update: [],
246
+ })
247
+ @TableColumn({
248
+ type: TableColumnType.Entity,
249
+ modelType: UserMicrosoftTeams,
250
+ manyToOneRelationColumn: "userMicrosoftTeamsId",
251
+ title: "User Microsoft Teams",
252
+ })
253
+ public userMicrosoftTeams?: UserMicrosoftTeams = undefined;
254
+
221
255
  @ColumnAccessControl({
222
256
  create: [Permission.CurrentUser],
223
257
  read: [Permission.CurrentUser, Permission.ProjectAdmin],
@@ -359,6 +393,41 @@ const METHOD_MODELS: Array<MethodModelUnderTest> = [
359
393
  labelColumns: ["userId", "projectId", "isVerified"],
360
394
  unmarkedSortColumn: "isVerified",
361
395
  },
396
+ {
397
+ name: "UserSlack",
398
+ modelType: UserSlack,
399
+ adminReadableModelType: AdminReadableUserSlack,
400
+ relationColumnName: "userSlack",
401
+ /*
402
+ * No verificationCode here: a Slack row is born verified off the user's own
403
+ * OAuth workspace link, so the two marked columns are the member id the bot
404
+ * delivers to and the display name that identifies the person in an outside
405
+ * workspace. Both are owner-readable, so the owner-only mark is the ONLY
406
+ * thing between them and any admin in the project.
407
+ */
408
+ ownerOnlyColumns: ["slackUserId", "slackUserName"],
409
+ ownerOnlyColumnsSelectableByOwner: ["slackUserId", "slackUserName"],
410
+ ownerOnlyColumnReachableThroughRelation: "slackUserId",
411
+ labelColumnReachableThroughRelation: "projectId",
412
+ labelColumns: ["userId", "projectId", "isVerified"],
413
+ unmarkedSortColumn: "isVerified",
414
+ },
415
+ {
416
+ name: "UserMicrosoftTeams",
417
+ modelType: UserMicrosoftTeams,
418
+ adminReadableModelType: AdminReadableUserMicrosoftTeams,
419
+ relationColumnName: "userMicrosoftTeams",
420
+ // Same shape as UserSlack: created verified, no verificationCode column.
421
+ ownerOnlyColumns: ["microsoftTeamsUserId", "microsoftTeamsUserName"],
422
+ ownerOnlyColumnsSelectableByOwner: [
423
+ "microsoftTeamsUserId",
424
+ "microsoftTeamsUserName",
425
+ ],
426
+ ownerOnlyColumnReachableThroughRelation: "microsoftTeamsUserId",
427
+ labelColumnReachableThroughRelation: "projectId",
428
+ labelColumns: ["userId", "projectId", "isVerified"],
429
+ unmarkedSortColumn: "isVerified",
430
+ },
362
431
  {
363
432
  name: "UserWebhook",
364
433
  modelType: UserWebhook,
@@ -1,8 +1,10 @@
1
1
  import TeamMember from "../../../../../Models/DatabaseModels/TeamMember";
2
2
  import UserCall from "../../../../../Models/DatabaseModels/UserCall";
3
3
  import UserEmail from "../../../../../Models/DatabaseModels/UserEmail";
4
+ import UserMicrosoftTeams from "../../../../../Models/DatabaseModels/UserMicrosoftTeams";
4
5
  import UserNotificationRule from "../../../../../Models/DatabaseModels/UserNotificationRule";
5
6
  import UserPush from "../../../../../Models/DatabaseModels/UserPush";
7
+ import UserSlack from "../../../../../Models/DatabaseModels/UserSlack";
6
8
  import UserSMS from "../../../../../Models/DatabaseModels/UserSMS";
7
9
  import UserTelegram from "../../../../../Models/DatabaseModels/UserTelegram";
8
10
  import UserWebhook from "../../../../../Models/DatabaseModels/UserWebhook";
@@ -27,8 +29,8 @@ import Permission from "../../../../../Types/Permission";
27
29
  * WHAT THIS FILE PINS
28
30
  *
29
31
  * On-call notification configuration - the rules themselves plus every channel
30
- * they can fire through (email, SMS, call, push, WhatsApp, Telegram, webhook) -
31
- * is row-scoped to its owner by one mechanism:
32
+ * they can fire through (email, SMS, call, push, WhatsApp, Telegram, Slack,
33
+ * Microsoft Teams, webhook) - is row-scoped to its owner by one mechanism:
32
34
  *
33
35
  * @TableAccessControl({ ... [Permission.CurrentUser] ... })
34
36
  * @CurrentUserCanAccessRecordBy("userId")
@@ -39,7 +41,7 @@ import Permission from "../../../../../Types/Permission";
39
41
  * addCurrentUserScopeToQuery() rewrites the query to `userId = me`, and throws
40
42
  * NotAuthorizedException outright if the caller had explicitly named somebody
41
43
  * else. The FIRST group below is the standing proof that this still holds for a
42
- * plain member on all eight models.
44
+ * plain member on all ten models.
43
45
  *
44
46
  * GAP D in Internal/Roadmap/OnCallNotificationReadiness.md was the consequence
45
47
  * for everybody else: a Project ADMIN was treated identically to a plain member
@@ -55,12 +57,12 @@ import Permission from "../../../../../Types/Permission";
55
57
  * a member's rules, repair them, delete them, and provision one for a
56
58
  * member who has none.
57
59
  *
58
- * - The SEVEN METHOD MODELS widen on NOTHING. All four lists are
60
+ * - The NINE METHOD MODELS widen on NOTHING. All four lists are
59
61
  * [CurrentUser], so an administrator is force-scoped onto their own rows
60
62
  * there and is rejected outright for naming somebody else, on every verb -
61
63
  * indistinguishable from a plain member.
62
64
  *
63
- * The read half of those seven WAS widened at one point, and reverting it is
65
+ * The read half of those models WAS widened at one point, and reverting it is
64
66
  * the most load-bearing fact in this file. The table scope is not one gate
65
67
  * among several on these models; it is the only one. Every column on them
66
68
  * declares `read: [Permission.CurrentUser]`, and that list is intersected by
@@ -97,7 +99,7 @@ import Permission from "../../../../../Types/Permission";
97
99
  * list. It is force-scoped for a member and unscoped for an admin. The
98
100
  * ownership decorator was therefore never the thing scoping admins out -
99
101
  * the absent permission entry was, which is why the rule table's fix is an
100
- * entry in a list rather than a new gate, and why the seven methods stay
102
+ * entry in a list rather than a new gate, and why the nine methods stay
101
103
  * scoped purely by having no such entry.
102
104
  *
103
105
  * - AdminReadableNotificationRuleModel below isolates the same shape on a
@@ -113,7 +115,7 @@ import Permission from "../../../../../Types/Permission";
113
115
  * production models, but a combination of verbs that none of them use.
114
116
  *
115
117
  * It is deliberately not a copy of any shipping model - not of the rule table,
116
- * whose four lists are all widened, and not of the seven methods, whose four
118
+ * whose four lists are all widened, and not of the nine methods, whose four
117
119
  * lists are all closed. Its job is to pin the mechanism - the scope lifts for
118
120
  * the operations whose list names a real permission and holds for the ones
119
121
  * whose list does not - so that the production assertions above it are testing
@@ -154,8 +156,8 @@ class AdminReadableNotificationRuleModel extends BaseModel {
154
156
  type ModelTypeUnderTest = { new (): BaseModel };
155
157
 
156
158
  /*
157
- * The seven channels a rule can fire through. They are seven copies of one
158
- * decorator shape, so every method assertion runs against all seven rather than
159
+ * The nine channels a rule can fire through. They are nine copies of one
160
+ * decorator shape, so every method assertion runs against all nine rather than
159
161
  * against a representative one - a widening applied to one of them is exactly
160
162
  * the kind of thing that survives review.
161
163
  */
@@ -166,16 +168,18 @@ const NOTIFICATION_METHOD_MODELS: Array<ModelTypeUnderTest> = [
166
168
  UserPush,
167
169
  UserWhatsApp,
168
170
  UserTelegram,
171
+ UserSlack,
172
+ UserMicrosoftTeams,
169
173
  UserWebhook,
170
174
  ];
171
175
 
172
176
  /*
173
177
  * Every model that makes up a user's on-call notification configuration.
174
178
  *
175
- * All eight behave identically for a plain member, which is what the first
179
+ * All ten behave identically for a plain member, which is what the first
176
180
  * group asserts over this list. They diverge only in what an ADMINISTRATOR
177
181
  * sees, so the admin-facing groups below name UserNotificationRule separately
178
- * and iterate NOTIFICATION_METHOD_MODELS for the seven that stay closed.
182
+ * and iterate NOTIFICATION_METHOD_MODELS for the nine that stay closed.
179
183
  */
180
184
  const NOTIFICATION_CONFIG_MODELS: Array<ModelTypeUnderTest> = [
181
185
  UserNotificationRule,
@@ -194,7 +198,7 @@ const ROW_SCOPED_REQUEST_TYPES: Array<DatabaseRequestType> = [
194
198
  * the model equals itself; naming them independently means a change to a
195
199
  * shipping list has to be re-stated deliberately in a test.
196
200
  *
197
- * On the seven method models these names appear only inside a `not.toContain`.
201
+ * On the nine method models these names appear only inside a `not.toContain`.
198
202
  */
199
203
  const ADMIN_READ_GRANTS: Array<Permission> = [
200
204
  Permission.ProjectOwner,
@@ -554,10 +558,10 @@ describe("GAP D closed - the rule table opens to a ProjectAdmin on every verb",
554
558
  });
555
559
  });
556
560
 
557
- describe("GAP D closed on the rule table alone - the seven methods open to nobody", () => {
561
+ describe("GAP D closed on the rule table alone - the nine methods open to nobody", () => {
558
562
  /*
559
563
  * THE BOUNDARY OF THE PHASE, AND THE PART MOST LIKELY TO BE "TIDIED UP"
560
- * LATER, because seven models that differ from their sibling in all four
564
+ * LATER, because nine models that differ from their sibling in all four
561
565
  * lists look like an oversight rather than a decision.
562
566
  *
563
567
  * Two independent reasons keep them closed, and both have to be understood or
@@ -736,7 +740,7 @@ describe("TeamMember proves the mechanism - a listed admin permission lifts the
736
740
  *
737
741
  * That is the whole of what the rule table gained, on a model that has
738
742
  * always shipped it - which is why the fix there was an entry in a list
739
- * rather than a new gate, and equally why the seven method models stay
743
+ * rather than a new gate, and equally why the nine method models stay
740
744
  * closed by simply having no such entry. Nothing guards them beyond the
741
745
  * absence, so this group stays here as the independent statement of what
742
746
  * the absence is doing.
@@ -842,14 +846,14 @@ describe("TeamMember proves the mechanism - a listed admin permission lifts the
842
846
 
843
847
  describe("the lift mechanism in isolation, on a throwaway model", () => {
844
848
  /*
845
- * The production groups above assert what UserNotificationRule and the seven
849
+ * The production groups above assert what UserNotificationRule and the nine
846
850
  * methods DO; this group asserts what the framework does with any user-scoped
847
851
  * model, on a class whose lists match neither of them - all four widened on
848
852
  * the rule table, all four closed on the methods, two-of-four here.
849
853
  *
850
854
  * That mismatch is the point. If a future change to TenantPermission alters
851
855
  * the rule, this group fails on its own terms and says so, instead of the
852
- * failure arriving disguised as eight models having drifted.
856
+ * failure arriving disguised as ten models having drifted.
853
857
  */
854
858
 
855
859
  it("lifts the ownership scope for a ProjectAdmin on read and update", async () => {
@@ -989,7 +993,7 @@ describe("notification config model decorators", () => {
989
993
  * while "restoring the admin feature" - and re-adding it silently removes
990
994
  * the row scope that every column on the model depends on. Neither
991
995
  * ADMIN_READ_GRANTS nor ADMIN_WRITE_GRANTS belongs in any list on these
992
- * seven; there is no verb on a notification method that an administrator
996
+ * nine; there is no verb on a notification method that an administrator
993
997
  * may perform on somebody else's row.
994
998
  */
995
999
  const model: BaseModel = new modelType();
@@ -0,0 +1,178 @@
1
+ import DatabaseRequestType from "../../../../../Server/Types/BaseDatabase/DatabaseRequestType";
2
+ import ColumnPermissions from "../../../../../Server/Types/Database/Permissions/ColumnPermission";
3
+ import UserSlack from "../../../../../Models/DatabaseModels/UserSlack";
4
+ import UserMicrosoftTeams from "../../../../../Models/DatabaseModels/UserMicrosoftTeams";
5
+ import DatabaseCommonInteractionProps from "../../../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
6
+ import ObjectID from "../../../../../Types/ObjectID";
7
+ import Permission, {
8
+ UserTenantAccessPermission,
9
+ } from "../../../../../Types/Permission";
10
+ import { describe, expect, it } from "@jest/globals";
11
+
12
+ /*
13
+ * REGRESSION PIN for the defect that made the whole feature dead on arrival.
14
+ *
15
+ * UserSlackService / UserMicrosoftTeamsService stamp the workspace identifier,
16
+ * the display name and isVerified onto createBy.data inside onBeforeCreate.
17
+ * DatabaseService.create runs that hook FIRST and only then calls
18
+ * ModelPermission.checkCreatePermissions on the STAMPED data — so the column
19
+ * ACLs are evaluated against values the SERVER wrote, with the CALLER's
20
+ * permissions. When those three columns carried `create: []`, every non-root
21
+ * create was refused with "User is not allowed to create on slackUserId
22
+ * column of Slack Account": no user could ever add Slack or Microsoft Teams
23
+ * as a notification method through the API or the dashboard, and no test
24
+ * noticed because the service suites call the hook directly rather than
25
+ * DatabaseService.create.
26
+ *
27
+ * The columns therefore carry `create: [Permission.CurrentUser]` — the same
28
+ * shape UserWebAuthn uses for its server-stamped isVerified — and the thing
29
+ * that keeps the values out of the CLIENT's hands is the hook itself, which
30
+ * refuses any non-root payload carrying them BEFORE it stamps. These tests
31
+ * pin both halves: the stamped shape passes the column check, and the check
32
+ * itself is really being evaluated (the harness guard).
33
+ */
34
+
35
+ const projectId: ObjectID = ObjectID.generate();
36
+ const userId: ObjectID = ObjectID.generate();
37
+
38
+ function makeProps(
39
+ permissions: Array<Permission>,
40
+ ): DatabaseCommonInteractionProps {
41
+ const tenantPermission: UserTenantAccessPermission = {
42
+ projectId,
43
+ _type: "UserTenantAccessPermission",
44
+ permissions: permissions.map((permission: Permission) => {
45
+ return {
46
+ _type: "UserPermission",
47
+ permission: permission,
48
+ labelIds: [],
49
+ isBlockPermission: false,
50
+ };
51
+ }),
52
+ };
53
+
54
+ return {
55
+ userId,
56
+ tenantId: projectId,
57
+ userTenantAccessPermission: {
58
+ [projectId.toString()]: tenantPermission,
59
+ },
60
+ };
61
+ }
62
+
63
+ /* Exactly what UserSlackService.onBeforeCreate leaves on createBy.data. */
64
+ function stampedUserSlack(): UserSlack {
65
+ const model: UserSlack = new UserSlack();
66
+ model.projectId = projectId;
67
+ model.userId = userId;
68
+ model.slackUserId = "U0123ABCD";
69
+ model.slackUserName = "alice";
70
+ model.isVerified = true;
71
+ return model;
72
+ }
73
+
74
+ /* Exactly what UserMicrosoftTeamsService.onBeforeCreate leaves behind. */
75
+ function stampedUserMicrosoftTeams(): UserMicrosoftTeams {
76
+ const model: UserMicrosoftTeams = new UserMicrosoftTeams();
77
+ model.projectId = projectId;
78
+ model.userId = userId;
79
+ model.microsoftTeamsUserId = "e6f1c1f7-aad0-4b6c-9c11-2f5b7c8d9e0f";
80
+ model.microsoftTeamsUserName = "Alice Example";
81
+ model.isVerified = true;
82
+ return model;
83
+ }
84
+
85
+ describe("server-stamped workspace method columns survive the create column check", () => {
86
+ /*
87
+ * Harness guard: a caller with NO permissions at all must still be refused,
88
+ * or every "does not throw" below would pass because the permission set was
89
+ * mis-built rather than because the columns are actually creatable.
90
+ * CurrentUser is auto-granted to authenticated callers by
91
+ * getUserPermissions, so the guard uses a column no list opens: userId's
92
+ * create list contains CurrentUser, but a props object with no userId is
93
+ * unauthenticated and gets nothing.
94
+ */
95
+ it("harness guard: an unauthenticated caller is still refused", () => {
96
+ const props: DatabaseCommonInteractionProps = makeProps([]);
97
+ delete (props as { userId?: ObjectID }).userId;
98
+
99
+ expect(() => {
100
+ ColumnPermissions.checkDataColumnPermissions(
101
+ UserSlack,
102
+ stampedUserSlack(),
103
+ props,
104
+ DatabaseRequestType.Create,
105
+ );
106
+ }).toThrow();
107
+ });
108
+
109
+ it("a stamped UserSlack passes for a plain authenticated member", () => {
110
+ expect(() => {
111
+ ColumnPermissions.checkDataColumnPermissions(
112
+ UserSlack,
113
+ stampedUserSlack(),
114
+ makeProps([Permission.CurrentUser]),
115
+ DatabaseRequestType.Create,
116
+ );
117
+ }).not.toThrow();
118
+ });
119
+
120
+ it("a stamped UserMicrosoftTeams passes for a plain authenticated member", () => {
121
+ expect(() => {
122
+ ColumnPermissions.checkDataColumnPermissions(
123
+ UserMicrosoftTeams,
124
+ stampedUserMicrosoftTeams(),
125
+ makeProps([Permission.CurrentUser]),
126
+ DatabaseRequestType.Create,
127
+ );
128
+ }).not.toThrow();
129
+ });
130
+
131
+ /*
132
+ * The other half of the design: opening the column ACL did NOT hand the
133
+ * value to the client — the SERVICE hook refuses any non-root payload that
134
+ * arrives carrying these columns, before anything is stamped. That refusal
135
+ * is pinned in UserSlackService.test.ts / UserMicrosoftTeamsService.test.ts
136
+ * ("cannot be set directly" / "cannot be set to true"); this file exists so
137
+ * that the two halves cannot drift apart unnoticed: if someone reverts the
138
+ * ACL to create: [], the two "passes" tests above fail; if someone removes
139
+ * the hook guards, the service suites fail.
140
+ */
141
+ it("update stays closed: even the owner cannot rewrite the stamped identifier", () => {
142
+ expect(() => {
143
+ ColumnPermissions.checkDataColumnPermissions(
144
+ UserSlack,
145
+ { slackUserId: "U-ATTACKER" } as UserSlack,
146
+ makeProps([Permission.CurrentUser]),
147
+ DatabaseRequestType.Update,
148
+ );
149
+ }).toThrow();
150
+ });
151
+
152
+ it("update stays closed on the Teams identifier too", () => {
153
+ expect(() => {
154
+ ColumnPermissions.checkDataColumnPermissions(
155
+ UserMicrosoftTeams,
156
+ { microsoftTeamsUserId: "spoofed" } as UserMicrosoftTeams,
157
+ makeProps([Permission.CurrentUser]),
158
+ DatabaseRequestType.Update,
159
+ );
160
+ }).toThrow();
161
+ });
162
+
163
+ it("update stays closed on isVerified for both models", () => {
164
+ for (const [modelType, data] of [
165
+ [UserSlack, { isVerified: true } as UserSlack],
166
+ [UserMicrosoftTeams, { isVerified: true } as UserMicrosoftTeams],
167
+ ] as Array<[{ new (): UserSlack | UserMicrosoftTeams }, UserSlack]>) {
168
+ expect(() => {
169
+ ColumnPermissions.checkDataColumnPermissions(
170
+ modelType,
171
+ data,
172
+ makeProps([Permission.CurrentUser]),
173
+ DatabaseRequestType.Update,
174
+ );
175
+ }).toThrow();
176
+ }
177
+ });
178
+ });