@oneuptime/common 11.0.3 → 11.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/Models/DatabaseModels/GlobalConfig.ts +19 -0
  2. package/Models/DatabaseModels/GlobalOidc.ts +351 -0
  3. package/Models/DatabaseModels/GlobalOidcProject.ts +265 -0
  4. package/Models/DatabaseModels/GlobalSso.ts +312 -0
  5. package/Models/DatabaseModels/GlobalSsoProject.ts +268 -0
  6. package/Models/DatabaseModels/Index.ts +8 -0
  7. package/Models/DatabaseModels/Project.ts +31 -0
  8. package/Models/DatabaseModels/StatusPage.ts +82 -0
  9. package/Server/API/StatusPageAPI.ts +2 -0
  10. package/Server/Infrastructure/Postgres/SchemaMigrations/{1781587937032-MigrationName.ts → 1781750000000-MigrationName.ts} +2 -2
  11. package/Server/Infrastructure/Postgres/SchemaMigrations/1782000000000-AddGlobalSsoAndOidc.ts +176 -0
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1782100000000-AddStatusPageImageAltText.ts +25 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1782200000000-AddRequireSsoForLoginToGlobalProviders.ts +25 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1782300000000-MoveRequireSsoForLoginToGlobalConfig.ts +38 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/1782310000000-MigrationName.ts +299 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1782400000000-RemoveIsTestedFromGlobalSsoAndOidc.ts +21 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +14 -2
  18. package/Server/Middleware/UserAuthorization.ts +113 -42
  19. package/Server/Services/GlobalConfigService.ts +50 -0
  20. package/Server/Services/GlobalOidcProjectService.ts +85 -0
  21. package/Server/Services/GlobalOidcService.ts +10 -0
  22. package/Server/Services/GlobalSsoProjectService.ts +85 -0
  23. package/Server/Services/GlobalSsoService.ts +10 -0
  24. package/Server/Services/Index.ts +8 -0
  25. package/Server/Services/ProjectService.ts +44 -1
  26. package/Server/Utils/Cookie.ts +39 -5
  27. package/Server/Utils/JsonWebToken.ts +7 -0
  28. package/Server/Utils/ValidateGlobalProviderProjectTeams.ts +119 -0
  29. package/Tests/Server/Middleware/UserAuthorization.test.ts +51 -13
  30. package/Tests/Server/Middleware/UserAuthorizationSSOProvider.test.ts +163 -0
  31. package/Tests/Server/Utils/CookieSSOToken.test.ts +130 -0
  32. package/Types/JsonWebTokenData.ts +3 -0
  33. package/Types/SSO/SsoProviderType.ts +8 -0
  34. package/UI/Components/Accordion/Accordion.tsx +5 -1
  35. package/UI/Components/CardSelect/CardSelect.tsx +6 -1
  36. package/UI/Components/CategoryCheckbox/Index.tsx +2 -1
  37. package/UI/Components/CodeEditor/CodeEditor.tsx +2 -0
  38. package/UI/Components/CollapsibleSection/CollapsibleSection.tsx +8 -1
  39. package/UI/Components/Dropdown/Dropdown.tsx +2 -0
  40. package/UI/Components/EntityDropdown/EntityDropdown.tsx +3 -0
  41. package/UI/Components/FilePicker/FilePicker.tsx +2 -0
  42. package/UI/Components/Forms/Fields/ColorPicker.tsx +2 -0
  43. package/UI/Components/Forms/Fields/FieldLabel.tsx +4 -0
  44. package/UI/Components/Forms/Fields/FormField.tsx +72 -15
  45. package/UI/Components/Forms/Fields/IconPicker.tsx +2 -0
  46. package/UI/Components/Forms/Validation.ts +107 -23
  47. package/UI/Components/Input/Input.tsx +4 -0
  48. package/UI/Components/Link/Link.tsx +23 -0
  49. package/UI/Components/Markdown.tsx/MarkdownConverters.ts +0 -0
  50. package/UI/Components/Markdown.tsx/MarkdownEditor.tsx +3 -0
  51. package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +63 -2
  52. package/UI/Components/Radio/Radio.tsx +2 -0
  53. package/UI/Components/RadioButtons/GroupRadioButtons.tsx +6 -1
  54. package/UI/Components/Tabs/Tabs.tsx +63 -0
  55. package/UI/Components/TextArea/TextArea.tsx +2 -0
  56. package/UI/Components/TimePicker/TimePicker.tsx +2 -0
  57. package/UI/Components/Toggle/Toggle.tsx +2 -1
  58. package/UI/Components/Tooltip/Tooltip.tsx +6 -1
  59. package/build/dist/Models/DatabaseModels/GlobalConfig.js +20 -0
  60. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  61. package/build/dist/Models/DatabaseModels/GlobalOidc.js +379 -0
  62. package/build/dist/Models/DatabaseModels/GlobalOidc.js.map +1 -0
  63. package/build/dist/Models/DatabaseModels/GlobalOidcProject.js +276 -0
  64. package/build/dist/Models/DatabaseModels/GlobalOidcProject.js.map +1 -0
  65. package/build/dist/Models/DatabaseModels/GlobalSso.js +341 -0
  66. package/build/dist/Models/DatabaseModels/GlobalSso.js.map +1 -0
  67. package/build/dist/Models/DatabaseModels/GlobalSsoProject.js +279 -0
  68. package/build/dist/Models/DatabaseModels/GlobalSsoProject.js.map +1 -0
  69. package/build/dist/Models/DatabaseModels/Index.js +8 -0
  70. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  71. package/build/dist/Models/DatabaseModels/Project.js +32 -0
  72. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  73. package/build/dist/Models/DatabaseModels/StatusPage.js +84 -0
  74. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  75. package/build/dist/Server/API/StatusPageAPI.js +2 -0
  76. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  77. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/{1781587937032-MigrationName.js → 1781750000000-MigrationName.js} +3 -3
  78. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/{1781587937032-MigrationName.js.map → 1781750000000-MigrationName.js.map} +1 -1
  79. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782000000000-AddGlobalSsoAndOidc.js +73 -0
  80. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782000000000-AddGlobalSsoAndOidc.js.map +1 -0
  81. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782100000000-AddStatusPageImageAltText.js +14 -0
  82. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782100000000-AddStatusPageImageAltText.js.map +1 -0
  83. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782200000000-AddRequireSsoForLoginToGlobalProviders.js +14 -0
  84. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782200000000-AddRequireSsoForLoginToGlobalProviders.js.map +1 -0
  85. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782300000000-MoveRequireSsoForLoginToGlobalConfig.js +23 -0
  86. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782300000000-MoveRequireSsoForLoginToGlobalConfig.js.map +1 -0
  87. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782310000000-MigrationName.js +106 -0
  88. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782310000000-MigrationName.js.map +1 -0
  89. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782400000000-RemoveIsTestedFromGlobalSsoAndOidc.js +14 -0
  90. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782400000000-RemoveIsTestedFromGlobalSsoAndOidc.js.map +1 -0
  91. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +14 -2
  92. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  93. package/build/dist/Server/Middleware/UserAuthorization.js +77 -34
  94. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  95. package/build/dist/Server/Services/GlobalConfigService.js +55 -0
  96. package/build/dist/Server/Services/GlobalConfigService.js.map +1 -1
  97. package/build/dist/Server/Services/GlobalOidcProjectService.js +80 -0
  98. package/build/dist/Server/Services/GlobalOidcProjectService.js.map +1 -0
  99. package/build/dist/Server/Services/GlobalOidcService.js +9 -0
  100. package/build/dist/Server/Services/GlobalOidcService.js.map +1 -0
  101. package/build/dist/Server/Services/GlobalSsoProjectService.js +80 -0
  102. package/build/dist/Server/Services/GlobalSsoProjectService.js.map +1 -0
  103. package/build/dist/Server/Services/GlobalSsoService.js +9 -0
  104. package/build/dist/Server/Services/GlobalSsoService.js.map +1 -0
  105. package/build/dist/Server/Services/Index.js +8 -0
  106. package/build/dist/Server/Services/Index.js.map +1 -1
  107. package/build/dist/Server/Services/ProjectService.js +36 -1
  108. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  109. package/build/dist/Server/Utils/Cookie.js +32 -3
  110. package/build/dist/Server/Utils/Cookie.js.map +1 -1
  111. package/build/dist/Server/Utils/JsonWebToken.js +6 -0
  112. package/build/dist/Server/Utils/JsonWebToken.js.map +1 -1
  113. package/build/dist/Server/Utils/ValidateGlobalProviderProjectTeams.js +66 -0
  114. package/build/dist/Server/Utils/ValidateGlobalProviderProjectTeams.js.map +1 -0
  115. package/build/dist/Types/SSO/SsoProviderType.js +9 -0
  116. package/build/dist/Types/SSO/SsoProviderType.js.map +1 -0
  117. package/build/dist/UI/Components/Accordion/Accordion.js +5 -3
  118. package/build/dist/UI/Components/Accordion/Accordion.js.map +1 -1
  119. package/build/dist/UI/Components/CardSelect/CardSelect.js +1 -1
  120. package/build/dist/UI/Components/CardSelect/CardSelect.js.map +1 -1
  121. package/build/dist/UI/Components/CategoryCheckbox/Index.js +1 -1
  122. package/build/dist/UI/Components/CategoryCheckbox/Index.js.map +1 -1
  123. package/build/dist/UI/Components/CodeEditor/CodeEditor.js +1 -1
  124. package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
  125. package/build/dist/UI/Components/CollapsibleSection/CollapsibleSection.js +4 -2
  126. package/build/dist/UI/Components/CollapsibleSection/CollapsibleSection.js.map +1 -1
  127. package/build/dist/UI/Components/Dropdown/Dropdown.js +1 -1
  128. package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
  129. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +2 -2
  130. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
  131. package/build/dist/UI/Components/FilePicker/FilePicker.js +1 -1
  132. package/build/dist/UI/Components/FilePicker/FilePicker.js.map +1 -1
  133. package/build/dist/UI/Components/Forms/Fields/ColorPicker.js +1 -1
  134. package/build/dist/UI/Components/Forms/Fields/ColorPicker.js.map +1 -1
  135. package/build/dist/UI/Components/Forms/Fields/FieldLabel.js +1 -1
  136. package/build/dist/UI/Components/Forms/Fields/FieldLabel.js.map +1 -1
  137. package/build/dist/UI/Components/Forms/Fields/FormField.js +58 -22
  138. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  139. package/build/dist/UI/Components/Forms/Fields/IconPicker.js +1 -1
  140. package/build/dist/UI/Components/Forms/Fields/IconPicker.js.map +1 -1
  141. package/build/dist/UI/Components/Forms/Validation.js +64 -15
  142. package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
  143. package/build/dist/UI/Components/Input/Input.js +1 -1
  144. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  145. package/build/dist/UI/Components/Link/Link.js +22 -1
  146. package/build/dist/UI/Components/Link/Link.js.map +1 -1
  147. package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js +0 -0
  148. package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js.map +1 -1
  149. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js +2 -2
  150. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js.map +1 -1
  151. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +46 -2
  152. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
  153. package/build/dist/UI/Components/Radio/Radio.js +1 -1
  154. package/build/dist/UI/Components/Radio/Radio.js.map +1 -1
  155. package/build/dist/UI/Components/RadioButtons/GroupRadioButtons.js +1 -1
  156. package/build/dist/UI/Components/RadioButtons/GroupRadioButtons.js.map +1 -1
  157. package/build/dist/UI/Components/Tabs/Tabs.js +50 -1
  158. package/build/dist/UI/Components/Tabs/Tabs.js.map +1 -1
  159. package/build/dist/UI/Components/TextArea/TextArea.js +1 -1
  160. package/build/dist/UI/Components/TextArea/TextArea.js.map +1 -1
  161. package/build/dist/UI/Components/TimePicker/TimePicker.js +1 -1
  162. package/build/dist/UI/Components/TimePicker/TimePicker.js.map +1 -1
  163. package/build/dist/UI/Components/Toggle/Toggle.js +1 -1
  164. package/build/dist/UI/Components/Toggle/Toggle.js.map +1 -1
  165. package/build/dist/UI/Components/Tooltip/Tooltip.js +6 -1
  166. package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
  167. package/package.json +1 -1
@@ -0,0 +1,299 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class MigrationName1782310000000 implements MigrationInterface {
4
+ public name = "MigrationName1782310000000";
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(
8
+ `ALTER TABLE "GlobalSSO" DROP CONSTRAINT "FK_GlobalSSO_createdByUserId"`,
9
+ );
10
+ await queryRunner.query(
11
+ `ALTER TABLE "GlobalSSO" DROP CONSTRAINT "FK_GlobalSSO_deletedByUserId"`,
12
+ );
13
+ await queryRunner.query(
14
+ `ALTER TABLE "GlobalOIDC" DROP CONSTRAINT "FK_GlobalOIDC_createdByUserId"`,
15
+ );
16
+ await queryRunner.query(
17
+ `ALTER TABLE "GlobalOIDC" DROP CONSTRAINT "FK_GlobalOIDC_deletedByUserId"`,
18
+ );
19
+ await queryRunner.query(
20
+ `ALTER TABLE "GlobalSSOProject" DROP CONSTRAINT "FK_GlobalSSOProject_globalSsoId"`,
21
+ );
22
+ await queryRunner.query(
23
+ `ALTER TABLE "GlobalSSOProject" DROP CONSTRAINT "FK_GlobalSSOProject_projectId"`,
24
+ );
25
+ await queryRunner.query(
26
+ `ALTER TABLE "GlobalSSOProject" DROP CONSTRAINT "FK_GlobalSSOProject_createdByUserId"`,
27
+ );
28
+ await queryRunner.query(
29
+ `ALTER TABLE "GlobalOIDCProject" DROP CONSTRAINT "FK_GlobalOIDCProject_globalOidcId"`,
30
+ );
31
+ await queryRunner.query(
32
+ `ALTER TABLE "GlobalOIDCProject" DROP CONSTRAINT "FK_GlobalOIDCProject_projectId"`,
33
+ );
34
+ await queryRunner.query(
35
+ `ALTER TABLE "GlobalOIDCProject" DROP CONSTRAINT "FK_GlobalOIDCProject_createdByUserId"`,
36
+ );
37
+ await queryRunner.query(
38
+ `ALTER TABLE "GlobalSSOProjectTeam" DROP CONSTRAINT "FK_GlobalSSOProjectTeam_globalSsoProjectId"`,
39
+ );
40
+ await queryRunner.query(
41
+ `ALTER TABLE "GlobalSSOProjectTeam" DROP CONSTRAINT "FK_GlobalSSOProjectTeam_teamId"`,
42
+ );
43
+ await queryRunner.query(
44
+ `ALTER TABLE "GlobalOIDCProjectTeam" DROP CONSTRAINT "FK_GlobalOIDCProjectTeam_globalOidcProjectId"`,
45
+ );
46
+ await queryRunner.query(
47
+ `ALTER TABLE "GlobalOIDCProjectTeam" DROP CONSTRAINT "FK_GlobalOIDCProjectTeam_teamId"`,
48
+ );
49
+ await queryRunner.query(
50
+ `DROP INDEX "public"."IDX_GlobalSSOProject_globalSsoId"`,
51
+ );
52
+ await queryRunner.query(
53
+ `DROP INDEX "public"."IDX_GlobalSSOProject_projectId"`,
54
+ );
55
+ await queryRunner.query(
56
+ `DROP INDEX "public"."IDX_GlobalSSOProject_globalSsoId_projectId"`,
57
+ );
58
+ await queryRunner.query(
59
+ `DROP INDEX "public"."IDX_GlobalOIDCProject_globalOidcId"`,
60
+ );
61
+ await queryRunner.query(
62
+ `DROP INDEX "public"."IDX_GlobalOIDCProject_projectId"`,
63
+ );
64
+ await queryRunner.query(
65
+ `DROP INDEX "public"."IDX_GlobalOIDCProject_globalOidcId_projectId"`,
66
+ );
67
+ await queryRunner.query(
68
+ `DROP INDEX "public"."IDX_GlobalSSOProjectTeam_globalSsoProjectId"`,
69
+ );
70
+ await queryRunner.query(
71
+ `DROP INDEX "public"."IDX_GlobalSSOProjectTeam_teamId"`,
72
+ );
73
+ await queryRunner.query(
74
+ `DROP INDEX "public"."IDX_GlobalOIDCProjectTeam_globalOidcProjectId"`,
75
+ );
76
+ await queryRunner.query(
77
+ `DROP INDEX "public"."IDX_GlobalOIDCProjectTeam_teamId"`,
78
+ );
79
+ await queryRunner.query(
80
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type":"Recurring","value":{"intervalType":"Day","intervalCount":{"_type":"PositiveNumber","value":1}}}'`,
81
+ );
82
+ await queryRunner.query(
83
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type":"RestrictionTimes","value":{"restictionType":"None","dayRestrictionTimes":null,"weeklyRestrictionTimes":[]}}'`,
84
+ );
85
+ await queryRunner.query(
86
+ `CREATE INDEX "IDX_71298cc376a253647839650fb0" ON "GlobalSSOProject" ("globalSsoId") `,
87
+ );
88
+ await queryRunner.query(
89
+ `CREATE INDEX "IDX_a58c789ff9fe63f416b0146084" ON "GlobalSSOProject" ("projectId") `,
90
+ );
91
+ await queryRunner.query(
92
+ `CREATE INDEX "IDX_8e4bf7b1571a065aad6feed4bc" ON "GlobalOIDCProject" ("globalOidcId") `,
93
+ );
94
+ await queryRunner.query(
95
+ `CREATE INDEX "IDX_e8522835aea64186bc53722f31" ON "GlobalOIDCProject" ("projectId") `,
96
+ );
97
+ await queryRunner.query(
98
+ `CREATE INDEX "IDX_390d2b4b9718741c13a9bf3646" ON "GlobalSSOProjectTeam" ("globalSsoProjectId") `,
99
+ );
100
+ await queryRunner.query(
101
+ `CREATE INDEX "IDX_c088ff9d9ca99dd93a8cbeec50" ON "GlobalSSOProjectTeam" ("teamId") `,
102
+ );
103
+ await queryRunner.query(
104
+ `CREATE INDEX "IDX_8c9b95c3d5d24e56c003467e82" ON "GlobalOIDCProjectTeam" ("globalOidcProjectId") `,
105
+ );
106
+ await queryRunner.query(
107
+ `CREATE INDEX "IDX_c11b9a707701e7c10b5b86c9a2" ON "GlobalOIDCProjectTeam" ("teamId") `,
108
+ );
109
+ await queryRunner.query(
110
+ `ALTER TABLE "GlobalSSO" ADD CONSTRAINT "FK_1003cf4114a89a4ff7ec16e7406" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
111
+ );
112
+ await queryRunner.query(
113
+ `ALTER TABLE "GlobalSSO" ADD CONSTRAINT "FK_4f520827496e355d4bdfda66120" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
114
+ );
115
+ await queryRunner.query(
116
+ `ALTER TABLE "GlobalOIDC" ADD CONSTRAINT "FK_b04eac8e1886aefae5e2b16cddc" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
117
+ );
118
+ await queryRunner.query(
119
+ `ALTER TABLE "GlobalOIDC" ADD CONSTRAINT "FK_0febb2e4c4d8703fce4bc816b36" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
120
+ );
121
+ await queryRunner.query(
122
+ `ALTER TABLE "GlobalSSOProject" ADD CONSTRAINT "FK_71298cc376a253647839650fb0b" FOREIGN KEY ("globalSsoId") REFERENCES "GlobalSSO"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
123
+ );
124
+ await queryRunner.query(
125
+ `ALTER TABLE "GlobalSSOProject" ADD CONSTRAINT "FK_a58c789ff9fe63f416b01460840" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
126
+ );
127
+ await queryRunner.query(
128
+ `ALTER TABLE "GlobalSSOProject" ADD CONSTRAINT "FK_91f47fc5d7c0f09bec7c9dd3e86" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
129
+ );
130
+ await queryRunner.query(
131
+ `ALTER TABLE "GlobalOIDCProject" ADD CONSTRAINT "FK_8e4bf7b1571a065aad6feed4bca" FOREIGN KEY ("globalOidcId") REFERENCES "GlobalOIDC"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
132
+ );
133
+ await queryRunner.query(
134
+ `ALTER TABLE "GlobalOIDCProject" ADD CONSTRAINT "FK_e8522835aea64186bc53722f314" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
135
+ );
136
+ await queryRunner.query(
137
+ `ALTER TABLE "GlobalOIDCProject" ADD CONSTRAINT "FK_63f1d796f2f259be00f402e936c" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
138
+ );
139
+ await queryRunner.query(
140
+ `ALTER TABLE "GlobalSSOProjectTeam" ADD CONSTRAINT "FK_390d2b4b9718741c13a9bf36467" FOREIGN KEY ("globalSsoProjectId") REFERENCES "GlobalSSOProject"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
141
+ );
142
+ await queryRunner.query(
143
+ `ALTER TABLE "GlobalSSOProjectTeam" ADD CONSTRAINT "FK_c088ff9d9ca99dd93a8cbeec50a" FOREIGN KEY ("teamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
144
+ );
145
+ await queryRunner.query(
146
+ `ALTER TABLE "GlobalOIDCProjectTeam" ADD CONSTRAINT "FK_8c9b95c3d5d24e56c003467e820" FOREIGN KEY ("globalOidcProjectId") REFERENCES "GlobalOIDCProject"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
147
+ );
148
+ await queryRunner.query(
149
+ `ALTER TABLE "GlobalOIDCProjectTeam" ADD CONSTRAINT "FK_c11b9a707701e7c10b5b86c9a21" FOREIGN KEY ("teamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
150
+ );
151
+ }
152
+
153
+ public async down(queryRunner: QueryRunner): Promise<void> {
154
+ await queryRunner.query(
155
+ `ALTER TABLE "GlobalOIDCProjectTeam" DROP CONSTRAINT "FK_c11b9a707701e7c10b5b86c9a21"`,
156
+ );
157
+ await queryRunner.query(
158
+ `ALTER TABLE "GlobalOIDCProjectTeam" DROP CONSTRAINT "FK_8c9b95c3d5d24e56c003467e820"`,
159
+ );
160
+ await queryRunner.query(
161
+ `ALTER TABLE "GlobalSSOProjectTeam" DROP CONSTRAINT "FK_c088ff9d9ca99dd93a8cbeec50a"`,
162
+ );
163
+ await queryRunner.query(
164
+ `ALTER TABLE "GlobalSSOProjectTeam" DROP CONSTRAINT "FK_390d2b4b9718741c13a9bf36467"`,
165
+ );
166
+ await queryRunner.query(
167
+ `ALTER TABLE "GlobalOIDCProject" DROP CONSTRAINT "FK_63f1d796f2f259be00f402e936c"`,
168
+ );
169
+ await queryRunner.query(
170
+ `ALTER TABLE "GlobalOIDCProject" DROP CONSTRAINT "FK_e8522835aea64186bc53722f314"`,
171
+ );
172
+ await queryRunner.query(
173
+ `ALTER TABLE "GlobalOIDCProject" DROP CONSTRAINT "FK_8e4bf7b1571a065aad6feed4bca"`,
174
+ );
175
+ await queryRunner.query(
176
+ `ALTER TABLE "GlobalSSOProject" DROP CONSTRAINT "FK_91f47fc5d7c0f09bec7c9dd3e86"`,
177
+ );
178
+ await queryRunner.query(
179
+ `ALTER TABLE "GlobalSSOProject" DROP CONSTRAINT "FK_a58c789ff9fe63f416b01460840"`,
180
+ );
181
+ await queryRunner.query(
182
+ `ALTER TABLE "GlobalSSOProject" DROP CONSTRAINT "FK_71298cc376a253647839650fb0b"`,
183
+ );
184
+ await queryRunner.query(
185
+ `ALTER TABLE "GlobalOIDC" DROP CONSTRAINT "FK_0febb2e4c4d8703fce4bc816b36"`,
186
+ );
187
+ await queryRunner.query(
188
+ `ALTER TABLE "GlobalOIDC" DROP CONSTRAINT "FK_b04eac8e1886aefae5e2b16cddc"`,
189
+ );
190
+ await queryRunner.query(
191
+ `ALTER TABLE "GlobalSSO" DROP CONSTRAINT "FK_4f520827496e355d4bdfda66120"`,
192
+ );
193
+ await queryRunner.query(
194
+ `ALTER TABLE "GlobalSSO" DROP CONSTRAINT "FK_1003cf4114a89a4ff7ec16e7406"`,
195
+ );
196
+ await queryRunner.query(
197
+ `DROP INDEX "public"."IDX_c11b9a707701e7c10b5b86c9a2"`,
198
+ );
199
+ await queryRunner.query(
200
+ `DROP INDEX "public"."IDX_8c9b95c3d5d24e56c003467e82"`,
201
+ );
202
+ await queryRunner.query(
203
+ `DROP INDEX "public"."IDX_c088ff9d9ca99dd93a8cbeec50"`,
204
+ );
205
+ await queryRunner.query(
206
+ `DROP INDEX "public"."IDX_390d2b4b9718741c13a9bf3646"`,
207
+ );
208
+ await queryRunner.query(
209
+ `DROP INDEX "public"."IDX_e8522835aea64186bc53722f31"`,
210
+ );
211
+ await queryRunner.query(
212
+ `DROP INDEX "public"."IDX_8e4bf7b1571a065aad6feed4bc"`,
213
+ );
214
+ await queryRunner.query(
215
+ `DROP INDEX "public"."IDX_a58c789ff9fe63f416b0146084"`,
216
+ );
217
+ await queryRunner.query(
218
+ `DROP INDEX "public"."IDX_71298cc376a253647839650fb0"`,
219
+ );
220
+ await queryRunner.query(
221
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type": "RestrictionTimes", "value": {"restictionType": "None", "dayRestrictionTimes": null, "weeklyRestrictionTimes": []}}'`,
222
+ );
223
+ await queryRunner.query(
224
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type": "Recurring", "value": {"intervalType": "Day", "intervalCount": {"_type": "PositiveNumber", "value": 1}}}'`,
225
+ );
226
+ await queryRunner.query(
227
+ `CREATE INDEX "IDX_GlobalOIDCProjectTeam_teamId" ON "GlobalOIDCProjectTeam" ("teamId") `,
228
+ );
229
+ await queryRunner.query(
230
+ `CREATE INDEX "IDX_GlobalOIDCProjectTeam_globalOidcProjectId" ON "GlobalOIDCProjectTeam" ("globalOidcProjectId") `,
231
+ );
232
+ await queryRunner.query(
233
+ `CREATE INDEX "IDX_GlobalSSOProjectTeam_teamId" ON "GlobalSSOProjectTeam" ("teamId") `,
234
+ );
235
+ await queryRunner.query(
236
+ `CREATE INDEX "IDX_GlobalSSOProjectTeam_globalSsoProjectId" ON "GlobalSSOProjectTeam" ("globalSsoProjectId") `,
237
+ );
238
+ await queryRunner.query(
239
+ `CREATE UNIQUE INDEX "IDX_GlobalOIDCProject_globalOidcId_projectId" ON "GlobalOIDCProject" ("globalOidcId", "projectId") `,
240
+ );
241
+ await queryRunner.query(
242
+ `CREATE INDEX "IDX_GlobalOIDCProject_projectId" ON "GlobalOIDCProject" ("projectId") `,
243
+ );
244
+ await queryRunner.query(
245
+ `CREATE INDEX "IDX_GlobalOIDCProject_globalOidcId" ON "GlobalOIDCProject" ("globalOidcId") `,
246
+ );
247
+ await queryRunner.query(
248
+ `CREATE UNIQUE INDEX "IDX_GlobalSSOProject_globalSsoId_projectId" ON "GlobalSSOProject" ("globalSsoId", "projectId") `,
249
+ );
250
+ await queryRunner.query(
251
+ `CREATE INDEX "IDX_GlobalSSOProject_projectId" ON "GlobalSSOProject" ("projectId") `,
252
+ );
253
+ await queryRunner.query(
254
+ `CREATE INDEX "IDX_GlobalSSOProject_globalSsoId" ON "GlobalSSOProject" ("globalSsoId") `,
255
+ );
256
+ await queryRunner.query(
257
+ `ALTER TABLE "GlobalOIDCProjectTeam" ADD CONSTRAINT "FK_GlobalOIDCProjectTeam_teamId" FOREIGN KEY ("teamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
258
+ );
259
+ await queryRunner.query(
260
+ `ALTER TABLE "GlobalOIDCProjectTeam" ADD CONSTRAINT "FK_GlobalOIDCProjectTeam_globalOidcProjectId" FOREIGN KEY ("globalOidcProjectId") REFERENCES "GlobalOIDCProject"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
261
+ );
262
+ await queryRunner.query(
263
+ `ALTER TABLE "GlobalSSOProjectTeam" ADD CONSTRAINT "FK_GlobalSSOProjectTeam_teamId" FOREIGN KEY ("teamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
264
+ );
265
+ await queryRunner.query(
266
+ `ALTER TABLE "GlobalSSOProjectTeam" ADD CONSTRAINT "FK_GlobalSSOProjectTeam_globalSsoProjectId" FOREIGN KEY ("globalSsoProjectId") REFERENCES "GlobalSSOProject"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
267
+ );
268
+ await queryRunner.query(
269
+ `ALTER TABLE "GlobalOIDCProject" ADD CONSTRAINT "FK_GlobalOIDCProject_createdByUserId" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
270
+ );
271
+ await queryRunner.query(
272
+ `ALTER TABLE "GlobalOIDCProject" ADD CONSTRAINT "FK_GlobalOIDCProject_projectId" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
273
+ );
274
+ await queryRunner.query(
275
+ `ALTER TABLE "GlobalOIDCProject" ADD CONSTRAINT "FK_GlobalOIDCProject_globalOidcId" FOREIGN KEY ("globalOidcId") REFERENCES "GlobalOIDC"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
276
+ );
277
+ await queryRunner.query(
278
+ `ALTER TABLE "GlobalSSOProject" ADD CONSTRAINT "FK_GlobalSSOProject_createdByUserId" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
279
+ );
280
+ await queryRunner.query(
281
+ `ALTER TABLE "GlobalSSOProject" ADD CONSTRAINT "FK_GlobalSSOProject_projectId" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
282
+ );
283
+ await queryRunner.query(
284
+ `ALTER TABLE "GlobalSSOProject" ADD CONSTRAINT "FK_GlobalSSOProject_globalSsoId" FOREIGN KEY ("globalSsoId") REFERENCES "GlobalSSO"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
285
+ );
286
+ await queryRunner.query(
287
+ `ALTER TABLE "GlobalOIDC" ADD CONSTRAINT "FK_GlobalOIDC_deletedByUserId" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
288
+ );
289
+ await queryRunner.query(
290
+ `ALTER TABLE "GlobalOIDC" ADD CONSTRAINT "FK_GlobalOIDC_createdByUserId" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
291
+ );
292
+ await queryRunner.query(
293
+ `ALTER TABLE "GlobalSSO" ADD CONSTRAINT "FK_GlobalSSO_deletedByUserId" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
294
+ );
295
+ await queryRunner.query(
296
+ `ALTER TABLE "GlobalSSO" ADD CONSTRAINT "FK_GlobalSSO_createdByUserId" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
297
+ );
298
+ }
299
+ }
@@ -0,0 +1,21 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class RemoveIsTestedFromGlobalSsoAndOidc1782400000000
4
+ implements MigrationInterface
5
+ {
6
+ public name = "RemoveIsTestedFromGlobalSsoAndOidc1782400000000";
7
+
8
+ public async up(queryRunner: QueryRunner): Promise<void> {
9
+ await queryRunner.query(`ALTER TABLE "GlobalSSO" DROP COLUMN "isTested"`);
10
+ await queryRunner.query(`ALTER TABLE "GlobalOIDC" DROP COLUMN "isTested"`);
11
+ }
12
+
13
+ public async down(queryRunner: QueryRunner): Promise<void> {
14
+ await queryRunner.query(
15
+ `ALTER TABLE "GlobalOIDC" ADD "isTested" boolean NOT NULL DEFAULT false`,
16
+ );
17
+ await queryRunner.query(
18
+ `ALTER TABLE "GlobalSSO" ADD "isTested" boolean NOT NULL DEFAULT false`,
19
+ );
20
+ }
21
+ }
@@ -386,10 +386,16 @@ import { MigrationName1781250074195 } from "./1781250074195-MigrationName";
386
386
  import { AddTelemetryEntityLabels1781300000000 } from "./1781300000000-AddTelemetryEntityLabels";
387
387
  import { AddServiceTelemetrySdkLanguage1781400000000 } from "./1781400000000-AddServiceTelemetrySdkLanguage";
388
388
  import { AddProxmoxAndCephClusterTables1781500000000 } from "./1781500000000-AddProxmoxAndCephClusterTables";
389
- import { MigrationName1781587937032 } from "./1781587937032-MigrationName";
390
389
  import { AddProxmoxCephV2Columns1781600000000 } from "./1781600000000-AddProxmoxCephV2Columns";
391
390
  import { AddProxmoxCephActivityAndRules1781600000001 } from "./1781600000001-AddProxmoxCephActivityAndRules";
392
391
  import { AddProxmoxCephV3Columns1781700000000 } from "./1781700000000-AddProxmoxCephV3Columns";
392
+ import { MigrationName1781750000000 } from "./1781750000000-MigrationName";
393
+ import { AddGlobalSsoAndOidc1782000000000 } from "./1782000000000-AddGlobalSsoAndOidc";
394
+ import { AddStatusPageImageAltText1782100000000 } from "./1782100000000-AddStatusPageImageAltText";
395
+ import { AddRequireSsoForLoginToGlobalProviders1782200000000 } from "./1782200000000-AddRequireSsoForLoginToGlobalProviders";
396
+ import { MoveRequireSsoForLoginToGlobalConfig1782300000000 } from "./1782300000000-MoveRequireSsoForLoginToGlobalConfig";
397
+ import { MigrationName1782310000000 } from "./1782310000000-MigrationName";
398
+ import { RemoveIsTestedFromGlobalSsoAndOidc1782400000000 } from "./1782400000000-RemoveIsTestedFromGlobalSsoAndOidc";
393
399
 
394
400
  export default [
395
401
  InitialMigration,
@@ -783,5 +789,11 @@ export default [
783
789
  AddProxmoxCephV2Columns1781600000000,
784
790
  AddProxmoxCephActivityAndRules1781600000001,
785
791
  AddProxmoxCephV3Columns1781700000000,
786
- MigrationName1781587937032,
792
+ MigrationName1781750000000,
793
+ AddGlobalSsoAndOidc1782000000000,
794
+ AddStatusPageImageAltText1782100000000,
795
+ AddRequireSsoForLoginToGlobalProviders1782200000000,
796
+ MoveRequireSsoForLoginToGlobalConfig1782300000000,
797
+ MigrationName1782310000000,
798
+ RemoveIsTestedFromGlobalSsoAndOidc1782400000000,
787
799
  ];
@@ -1,8 +1,8 @@
1
1
  import AccessTokenService from "../Services/AccessTokenService";
2
+ import GlobalConfigService from "../Services/GlobalConfigService";
2
3
  import ProjectService from "../Services/ProjectService";
3
4
  import TeamMemberService from "../Services/TeamMemberService";
4
5
  import UserService from "../Services/UserService";
5
- import QueryHelper from "../Types/Database/QueryHelper";
6
6
  import CookieUtil from "../Utils/Cookie";
7
7
  import {
8
8
  ExpressRequest,
@@ -16,7 +16,6 @@ import logger, { getLogAttributesFromRequest } from "../Utils/Logger";
16
16
  import Response from "../Utils/Response";
17
17
  import ProjectMiddleware from "./ProjectAuthorization";
18
18
  import SpanUtil from "../Utils/Telemetry/SpanUtil";
19
- import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
20
19
  import Dictionary from "../../Types/Dictionary";
21
20
  import Exception from "../../Types/Exception/Exception";
22
21
  import NotAuthenticatedException from "../../Types/Exception/NotAuthenticatedException";
@@ -35,7 +34,6 @@ import Permission, {
35
34
  UserTenantAccessPermission,
36
35
  } from "../../Types/Permission";
37
36
  import UserType from "../../Types/UserType";
38
- import Project from "../../Models/DatabaseModels/Project";
39
37
  import UserPermissionUtil from "../Utils/UserPermission/UserPermission";
40
38
 
41
39
  export default class UserMiddleware {
@@ -170,6 +168,7 @@ export default class UserMiddleware {
170
168
  req: ExpressRequest,
171
169
  projectId: ObjectID,
172
170
  userId: ObjectID,
171
+ requiredSsoProviderId?: ObjectID | undefined,
173
172
  ): boolean {
174
173
  const ssoTokens: Dictionary<string> = this.getSsoTokens(req);
175
174
 
@@ -181,6 +180,25 @@ export default class UserMiddleware {
181
180
  decodedData.projectId?.toString() === projectId.toString() &&
182
181
  decodedData.userId.toString() === userId.toString()
183
182
  ) {
183
+ /*
184
+ * Specific-IdP enforcement: when the project requires a specific SSO
185
+ * provider, the token must carry a matching `ssoProviderId`
186
+ * discriminator. Tokens issued before this field existed (no
187
+ * discriminator) do not satisfy a specific-provider requirement.
188
+ */
189
+ if (requiredSsoProviderId) {
190
+ const tokenProviderId: string | undefined = decodedData.ssoProviderId
191
+ ? decodedData.ssoProviderId.toString()
192
+ : undefined;
193
+
194
+ if (
195
+ !tokenProviderId ||
196
+ tokenProviderId !== requiredSsoProviderId.toString()
197
+ ) {
198
+ return false;
199
+ }
200
+ }
201
+
184
202
  return true;
185
203
  }
186
204
  }
@@ -493,12 +511,15 @@ export default class UserMiddleware {
493
511
  }): Promise<UserTenantAccessPermission | null> {
494
512
  const { req, tenantId, userId } = data;
495
513
 
514
+ const isMasterAdmin: boolean =
515
+ (req as OneUptimeRequest).userAuthorization?.isMasterAdmin === true;
516
+
496
517
  /*
497
518
  * Resolve the SSO requirement and the tenant permission in parallel.
498
519
  * `getRequireSsoForLogin` is cached in-process for 60s, so this is
499
520
  * usually free; the tenant permission lookup is the expensive call.
500
521
  */
501
- const [requireSsoForLogin, tenantPermission]: [
522
+ const [projectRequireSsoForLogin, tenantPermission]: [
502
523
  boolean,
503
524
  UserTenantAccessPermission | null,
504
525
  ] = await Promise.all([
@@ -515,11 +536,42 @@ export default class UserMiddleware {
515
536
  AccessTokenService.getUserTenantAccessPermission(userId, tenantId),
516
537
  ]);
517
538
 
518
- if (
519
- requireSsoForLogin &&
520
- !UserMiddleware.doesSsoTokenForProjectExist(req, tenantId, userId)
521
- ) {
522
- throw new SsoAuthorizationException();
539
+ /*
540
+ * The instance-wide "Require SSO for Login" flag (GlobalConfig) forces SSO
541
+ * on every project. Master admins are exempt so a misconfigured global SSO
542
+ * can't lock them out — a project's own requireSsoForLogin still applies to
543
+ * them. Only checked when the project doesn't already enforce SSO.
544
+ */
545
+ let requireSsoForLogin: boolean = projectRequireSsoForLogin;
546
+ if (!requireSsoForLogin && !isMasterAdmin) {
547
+ requireSsoForLogin =
548
+ await GlobalConfigService.getRequireSsoForLogin().catch(() => {
549
+ return false;
550
+ });
551
+ }
552
+
553
+ if (requireSsoForLogin) {
554
+ /*
555
+ * Only resolve the specific-provider requirement when SSO is enforced.
556
+ * The provider-id cache is already warm from getRequireSsoForLogin above.
557
+ */
558
+ const requiredSsoProviderId: ObjectID | null =
559
+ await ProjectService.getRequireSsoWithSsoProviderId(tenantId).catch(
560
+ () => {
561
+ return null;
562
+ },
563
+ );
564
+
565
+ if (
566
+ !UserMiddleware.doesSsoTokenForProjectExist(
567
+ req,
568
+ tenantId,
569
+ userId,
570
+ requiredSsoProviderId ?? undefined,
571
+ )
572
+ ) {
573
+ throw new SsoAuthorizationException();
574
+ }
523
575
  }
524
576
 
525
577
  return tenantPermission;
@@ -535,48 +587,67 @@ export default class UserMiddleware {
535
587
  return null;
536
588
  }
537
589
 
538
- const projects: Array<Project> = await ProjectService.findBy({
539
- query: {
540
- _id: QueryHelper.any(
541
- projectIds.map((i: ObjectID) => {
542
- return i.toString();
543
- }) || [],
544
- ),
545
- },
546
- select: {
547
- requireSsoForLogin: true,
548
- },
549
- limit: LIMIT_PER_PROJECT,
550
- skip: 0,
551
- props: {
552
- isRoot: true,
553
- },
554
- });
590
+ const isMasterAdmin: boolean =
591
+ (req as OneUptimeRequest).userAuthorization?.isMasterAdmin === true;
592
+
593
+ /*
594
+ * Instance-wide "Require SSO for Login" forces SSO on every project. Master
595
+ * admins are exempt. Resolved once (cached) rather than per project.
596
+ */
597
+ const globalRequireSsoForLogin: boolean = isMasterAdmin
598
+ ? false
599
+ : await GlobalConfigService.getRequireSsoForLogin().catch(() => {
600
+ return false;
601
+ });
555
602
 
556
603
  /*
557
- * Resolve permissions for every project in parallel. With the previous
558
- * for-await loop this scaled linearly with project count, adding one
559
- * round-trip per project even on cache hits.
604
+ * Resolve permissions for every project in parallel. A project's own
605
+ * requireSsoForLogin is read through the cached getter; the global flag is
606
+ * OR'd in so enforcement matches the single-tenant path.
560
607
  */
561
608
  const resolved: Array<{
562
609
  projectId: ObjectID;
563
610
  permission: UserTenantAccessPermission | null;
564
611
  }> = await Promise.all(
565
612
  projectIds.map(async (projectId: ObjectID) => {
566
- if (
567
- projects.find((p: Project) => {
568
- return p._id === projectId.toString() && p.requireSsoForLogin;
569
- }) &&
570
- !UserMiddleware.doesSsoTokenForProjectExist(req, projectId, userId)
571
- ) {
572
- return {
573
- projectId,
574
- permission:
575
- UserPermissionUtil.getDefaultUserTenantAccessPermission(
576
- projectId,
577
- ),
578
- };
613
+ const projectRequireSsoForLogin: boolean =
614
+ await ProjectService.getRequireSsoForLogin(projectId).catch(() => {
615
+ /*
616
+ * Unknown/inaccessible project: do not enforce SSO here. Actual
617
+ * access is still gated by AccessTokenService below.
618
+ */
619
+ return false;
620
+ });
621
+
622
+ const requireSsoForLogin: boolean =
623
+ projectRequireSsoForLogin || globalRequireSsoForLogin;
624
+
625
+ if (requireSsoForLogin) {
626
+ const requiredSsoProviderId: ObjectID | null =
627
+ await ProjectService.getRequireSsoWithSsoProviderId(
628
+ projectId,
629
+ ).catch(() => {
630
+ return null;
631
+ });
632
+
633
+ if (
634
+ !UserMiddleware.doesSsoTokenForProjectExist(
635
+ req,
636
+ projectId,
637
+ userId,
638
+ requiredSsoProviderId ?? undefined,
639
+ )
640
+ ) {
641
+ return {
642
+ projectId,
643
+ permission:
644
+ UserPermissionUtil.getDefaultUserTenantAccessPermission(
645
+ projectId,
646
+ ),
647
+ };
648
+ }
579
649
  }
650
+
580
651
  return {
581
652
  projectId,
582
653
  permission: await AccessTokenService.getUserTenantAccessPermission(
@@ -1,10 +1,60 @@
1
1
  import DatabaseService from "./DatabaseService";
2
2
  import Model from "../../Models/DatabaseModels/GlobalConfig";
3
+ import InMemoryTTLCache from "../Infrastructure/InMemoryTTLCache";
4
+ import ObjectID from "../../Types/ObjectID";
5
+ import { OnUpdate } from "../Types/Database/Hooks";
6
+ import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
3
7
 
4
8
  export class Service extends DatabaseService<Model> {
9
+ /*
10
+ * Caches the instance-wide "Require SSO for Login" flag. This is read on the
11
+ * authenticated request path (per project), so it must not hit Postgres every
12
+ * time. Refreshed at most once per 60s and invalidated on update below.
13
+ */
14
+ private requireSsoForLoginCache: InMemoryTTLCache<boolean> =
15
+ new InMemoryTTLCache(10_000);
16
+
5
17
  public constructor() {
6
18
  super(Model);
7
19
  }
20
+
21
+ /*
22
+ * Instance-wide: must every user sign in with SSO to access projects?
23
+ * (Master admins are exempted by the enforcement layer, not here.)
24
+ */
25
+ @CaptureSpan()
26
+ public async getRequireSsoForLogin(): Promise<boolean> {
27
+ const key: string = "global";
28
+ const cached: boolean | undefined = this.requireSsoForLoginCache.get(key);
29
+ if (cached !== undefined) {
30
+ return cached;
31
+ }
32
+
33
+ const config: Model | null = await this.findOneBy({
34
+ query: {},
35
+ select: { requireSsoForLogin: true },
36
+ props: { isRoot: true },
37
+ });
38
+
39
+ const value: boolean = Boolean(config?.requireSsoForLogin);
40
+ this.requireSsoForLoginCache.set(key, value, 60_000);
41
+ return value;
42
+ }
43
+
44
+ @CaptureSpan()
45
+ protected override async onUpdateSuccess(
46
+ onUpdate: OnUpdate<Model>,
47
+ _updatedItemIds: Array<ObjectID>,
48
+ ): Promise<OnUpdate<Model>> {
49
+ if (
50
+ (onUpdate.updateBy.data as { requireSsoForLogin?: unknown })
51
+ .requireSsoForLogin !== undefined
52
+ ) {
53
+ this.requireSsoForLoginCache.clear();
54
+ }
55
+
56
+ return onUpdate;
57
+ }
8
58
  }
9
59
 
10
60
  export default new Service();