@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.
- package/Models/DatabaseModels/GlobalConfig.ts +19 -0
- package/Models/DatabaseModels/GlobalOidc.ts +351 -0
- package/Models/DatabaseModels/GlobalOidcProject.ts +265 -0
- package/Models/DatabaseModels/GlobalSso.ts +312 -0
- package/Models/DatabaseModels/GlobalSsoProject.ts +268 -0
- package/Models/DatabaseModels/Index.ts +8 -0
- package/Models/DatabaseModels/Project.ts +31 -0
- package/Models/DatabaseModels/StatusPage.ts +82 -0
- package/Server/API/StatusPageAPI.ts +2 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/{1781587937032-MigrationName.ts → 1781750000000-MigrationName.ts} +2 -2
- package/Server/Infrastructure/Postgres/SchemaMigrations/1782000000000-AddGlobalSsoAndOidc.ts +176 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1782100000000-AddStatusPageImageAltText.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1782200000000-AddRequireSsoForLoginToGlobalProviders.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1782300000000-MoveRequireSsoForLoginToGlobalConfig.ts +38 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1782310000000-MigrationName.ts +299 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1782400000000-RemoveIsTestedFromGlobalSsoAndOidc.ts +21 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +14 -2
- package/Server/Middleware/UserAuthorization.ts +113 -42
- package/Server/Services/GlobalConfigService.ts +50 -0
- package/Server/Services/GlobalOidcProjectService.ts +85 -0
- package/Server/Services/GlobalOidcService.ts +10 -0
- package/Server/Services/GlobalSsoProjectService.ts +85 -0
- package/Server/Services/GlobalSsoService.ts +10 -0
- package/Server/Services/Index.ts +8 -0
- package/Server/Services/ProjectService.ts +44 -1
- package/Server/Utils/Cookie.ts +39 -5
- package/Server/Utils/JsonWebToken.ts +7 -0
- package/Server/Utils/ValidateGlobalProviderProjectTeams.ts +119 -0
- package/Tests/Server/Middleware/UserAuthorization.test.ts +51 -13
- package/Tests/Server/Middleware/UserAuthorizationSSOProvider.test.ts +163 -0
- package/Tests/Server/Utils/CookieSSOToken.test.ts +130 -0
- package/Types/JsonWebTokenData.ts +3 -0
- package/Types/SSO/SsoProviderType.ts +8 -0
- package/UI/Components/Accordion/Accordion.tsx +5 -1
- package/UI/Components/CardSelect/CardSelect.tsx +6 -1
- package/UI/Components/CategoryCheckbox/Index.tsx +2 -1
- package/UI/Components/CodeEditor/CodeEditor.tsx +2 -0
- package/UI/Components/CollapsibleSection/CollapsibleSection.tsx +8 -1
- package/UI/Components/Dropdown/Dropdown.tsx +2 -0
- package/UI/Components/EntityDropdown/EntityDropdown.tsx +3 -0
- package/UI/Components/FilePicker/FilePicker.tsx +2 -0
- package/UI/Components/Forms/Fields/ColorPicker.tsx +2 -0
- package/UI/Components/Forms/Fields/FieldLabel.tsx +4 -0
- package/UI/Components/Forms/Fields/FormField.tsx +72 -15
- package/UI/Components/Forms/Fields/IconPicker.tsx +2 -0
- package/UI/Components/Forms/Validation.ts +107 -23
- package/UI/Components/Input/Input.tsx +4 -0
- package/UI/Components/Link/Link.tsx +23 -0
- package/UI/Components/Markdown.tsx/MarkdownConverters.ts +0 -0
- package/UI/Components/Markdown.tsx/MarkdownEditor.tsx +3 -0
- package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +63 -2
- package/UI/Components/Radio/Radio.tsx +2 -0
- package/UI/Components/RadioButtons/GroupRadioButtons.tsx +6 -1
- package/UI/Components/Tabs/Tabs.tsx +63 -0
- package/UI/Components/TextArea/TextArea.tsx +2 -0
- package/UI/Components/TimePicker/TimePicker.tsx +2 -0
- package/UI/Components/Toggle/Toggle.tsx +2 -1
- package/UI/Components/Tooltip/Tooltip.tsx +6 -1
- package/build/dist/Models/DatabaseModels/GlobalConfig.js +20 -0
- package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalOidc.js +379 -0
- package/build/dist/Models/DatabaseModels/GlobalOidc.js.map +1 -0
- package/build/dist/Models/DatabaseModels/GlobalOidcProject.js +276 -0
- package/build/dist/Models/DatabaseModels/GlobalOidcProject.js.map +1 -0
- package/build/dist/Models/DatabaseModels/GlobalSso.js +341 -0
- package/build/dist/Models/DatabaseModels/GlobalSso.js.map +1 -0
- package/build/dist/Models/DatabaseModels/GlobalSsoProject.js +279 -0
- package/build/dist/Models/DatabaseModels/GlobalSsoProject.js.map +1 -0
- package/build/dist/Models/DatabaseModels/Index.js +8 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +32 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +84 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +2 -0
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/{1781587937032-MigrationName.js → 1781750000000-MigrationName.js} +3 -3
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/{1781587937032-MigrationName.js.map → 1781750000000-MigrationName.js.map} +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782000000000-AddGlobalSsoAndOidc.js +73 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782000000000-AddGlobalSsoAndOidc.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782100000000-AddStatusPageImageAltText.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782100000000-AddStatusPageImageAltText.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782200000000-AddRequireSsoForLoginToGlobalProviders.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782200000000-AddRequireSsoForLoginToGlobalProviders.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782300000000-MoveRequireSsoForLoginToGlobalConfig.js +23 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782300000000-MoveRequireSsoForLoginToGlobalConfig.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782310000000-MigrationName.js +106 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782310000000-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782400000000-RemoveIsTestedFromGlobalSsoAndOidc.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782400000000-RemoveIsTestedFromGlobalSsoAndOidc.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +14 -2
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/UserAuthorization.js +77 -34
- package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
- package/build/dist/Server/Services/GlobalConfigService.js +55 -0
- package/build/dist/Server/Services/GlobalConfigService.js.map +1 -1
- package/build/dist/Server/Services/GlobalOidcProjectService.js +80 -0
- package/build/dist/Server/Services/GlobalOidcProjectService.js.map +1 -0
- package/build/dist/Server/Services/GlobalOidcService.js +9 -0
- package/build/dist/Server/Services/GlobalOidcService.js.map +1 -0
- package/build/dist/Server/Services/GlobalSsoProjectService.js +80 -0
- package/build/dist/Server/Services/GlobalSsoProjectService.js.map +1 -0
- package/build/dist/Server/Services/GlobalSsoService.js +9 -0
- package/build/dist/Server/Services/GlobalSsoService.js.map +1 -0
- package/build/dist/Server/Services/Index.js +8 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +36 -1
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Utils/Cookie.js +32 -3
- package/build/dist/Server/Utils/Cookie.js.map +1 -1
- package/build/dist/Server/Utils/JsonWebToken.js +6 -0
- package/build/dist/Server/Utils/JsonWebToken.js.map +1 -1
- package/build/dist/Server/Utils/ValidateGlobalProviderProjectTeams.js +66 -0
- package/build/dist/Server/Utils/ValidateGlobalProviderProjectTeams.js.map +1 -0
- package/build/dist/Types/SSO/SsoProviderType.js +9 -0
- package/build/dist/Types/SSO/SsoProviderType.js.map +1 -0
- package/build/dist/UI/Components/Accordion/Accordion.js +5 -3
- package/build/dist/UI/Components/Accordion/Accordion.js.map +1 -1
- package/build/dist/UI/Components/CardSelect/CardSelect.js +1 -1
- package/build/dist/UI/Components/CardSelect/CardSelect.js.map +1 -1
- package/build/dist/UI/Components/CategoryCheckbox/Index.js +1 -1
- package/build/dist/UI/Components/CategoryCheckbox/Index.js.map +1 -1
- package/build/dist/UI/Components/CodeEditor/CodeEditor.js +1 -1
- package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
- package/build/dist/UI/Components/CollapsibleSection/CollapsibleSection.js +4 -2
- package/build/dist/UI/Components/CollapsibleSection/CollapsibleSection.js.map +1 -1
- package/build/dist/UI/Components/Dropdown/Dropdown.js +1 -1
- package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
- package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +2 -2
- package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
- package/build/dist/UI/Components/FilePicker/FilePicker.js +1 -1
- package/build/dist/UI/Components/FilePicker/FilePicker.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/ColorPicker.js +1 -1
- package/build/dist/UI/Components/Forms/Fields/ColorPicker.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/FieldLabel.js +1 -1
- package/build/dist/UI/Components/Forms/Fields/FieldLabel.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js +58 -22
- package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/IconPicker.js +1 -1
- package/build/dist/UI/Components/Forms/Fields/IconPicker.js.map +1 -1
- package/build/dist/UI/Components/Forms/Validation.js +64 -15
- package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
- package/build/dist/UI/Components/Input/Input.js +1 -1
- package/build/dist/UI/Components/Input/Input.js.map +1 -1
- package/build/dist/UI/Components/Link/Link.js +22 -1
- package/build/dist/UI/Components/Link/Link.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js +0 -0
- package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js +2 -2
- package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +46 -2
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
- package/build/dist/UI/Components/Radio/Radio.js +1 -1
- package/build/dist/UI/Components/Radio/Radio.js.map +1 -1
- package/build/dist/UI/Components/RadioButtons/GroupRadioButtons.js +1 -1
- package/build/dist/UI/Components/RadioButtons/GroupRadioButtons.js.map +1 -1
- package/build/dist/UI/Components/Tabs/Tabs.js +50 -1
- package/build/dist/UI/Components/Tabs/Tabs.js.map +1 -1
- package/build/dist/UI/Components/TextArea/TextArea.js +1 -1
- package/build/dist/UI/Components/TextArea/TextArea.js.map +1 -1
- package/build/dist/UI/Components/TimePicker/TimePicker.js +1 -1
- package/build/dist/UI/Components/TimePicker/TimePicker.js.map +1 -1
- package/build/dist/UI/Components/Toggle/Toggle.js +1 -1
- package/build/dist/UI/Components/Toggle/Toggle.js.map +1 -1
- package/build/dist/UI/Components/Tooltip/Tooltip.js +6 -1
- package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import DatabaseService from "./DatabaseService";
|
|
2
|
+
import Model from "../../Models/DatabaseModels/GlobalOidcProject";
|
|
3
|
+
import Team from "../../Models/DatabaseModels/Team";
|
|
4
|
+
import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
|
|
5
|
+
import ObjectID from "../../Types/ObjectID";
|
|
6
|
+
import CreateBy from "../Types/Database/CreateBy";
|
|
7
|
+
import { OnCreate, OnUpdate } from "../Types/Database/Hooks";
|
|
8
|
+
import UpdateBy from "../Types/Database/UpdateBy";
|
|
9
|
+
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
10
|
+
import validateGlobalProviderProjectTeams, {
|
|
11
|
+
resolveAttachmentProjectId,
|
|
12
|
+
} from "../Utils/ValidateGlobalProviderProjectTeams";
|
|
13
|
+
|
|
14
|
+
export class Service extends DatabaseService<Model> {
|
|
15
|
+
public constructor() {
|
|
16
|
+
super(Model);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@CaptureSpan()
|
|
20
|
+
protected override async onBeforeCreate(
|
|
21
|
+
createBy: CreateBy<Model>,
|
|
22
|
+
): Promise<OnCreate<Model>> {
|
|
23
|
+
/*
|
|
24
|
+
* The attach form submits the project via the `project` relation, so the
|
|
25
|
+
* `projectId` FK is not set yet. Resolve it and persist it (the column is
|
|
26
|
+
* required / NOT NULL) before validating the default teams against it.
|
|
27
|
+
*/
|
|
28
|
+
const projectId: ObjectID | undefined = resolveAttachmentProjectId(
|
|
29
|
+
createBy.data,
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
if (projectId) {
|
|
33
|
+
createBy.data.projectId = projectId;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
await validateGlobalProviderProjectTeams({
|
|
37
|
+
teams: createBy.data.teams,
|
|
38
|
+
projectId,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return { createBy, carryForward: null };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@CaptureSpan()
|
|
45
|
+
protected override async onBeforeUpdate(
|
|
46
|
+
updateBy: UpdateBy<Model>,
|
|
47
|
+
): Promise<OnUpdate<Model>> {
|
|
48
|
+
// `updateBy.data` is a partial-entity shape; narrow the relation/id here.
|
|
49
|
+
const teams: Array<Team> | undefined = updateBy.data.teams as unknown as
|
|
50
|
+
| Array<Team>
|
|
51
|
+
| undefined;
|
|
52
|
+
|
|
53
|
+
if (teams && teams.length > 0) {
|
|
54
|
+
const explicitProjectId: ObjectID | undefined = updateBy.data
|
|
55
|
+
.projectId as unknown as ObjectID | undefined;
|
|
56
|
+
|
|
57
|
+
if (explicitProjectId) {
|
|
58
|
+
await validateGlobalProviderProjectTeams({
|
|
59
|
+
teams,
|
|
60
|
+
projectId: explicitProjectId,
|
|
61
|
+
});
|
|
62
|
+
} else {
|
|
63
|
+
// projectId is immutable here; resolve it from the row(s) being updated.
|
|
64
|
+
const rows: Array<Model> = await this.findBy({
|
|
65
|
+
query: updateBy.query,
|
|
66
|
+
select: { _id: true, projectId: true },
|
|
67
|
+
limit: LIMIT_PER_PROJECT,
|
|
68
|
+
skip: 0,
|
|
69
|
+
props: { isRoot: true },
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
for (const row of rows) {
|
|
73
|
+
await validateGlobalProviderProjectTeams({
|
|
74
|
+
teams,
|
|
75
|
+
projectId: row.projectId,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return { updateBy, carryForward: null };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export default new Service();
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import DatabaseService from "./DatabaseService";
|
|
2
|
+
import Model from "../../Models/DatabaseModels/GlobalSsoProject";
|
|
3
|
+
import Team from "../../Models/DatabaseModels/Team";
|
|
4
|
+
import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
|
|
5
|
+
import ObjectID from "../../Types/ObjectID";
|
|
6
|
+
import CreateBy from "../Types/Database/CreateBy";
|
|
7
|
+
import { OnCreate, OnUpdate } from "../Types/Database/Hooks";
|
|
8
|
+
import UpdateBy from "../Types/Database/UpdateBy";
|
|
9
|
+
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
10
|
+
import validateGlobalProviderProjectTeams, {
|
|
11
|
+
resolveAttachmentProjectId,
|
|
12
|
+
} from "../Utils/ValidateGlobalProviderProjectTeams";
|
|
13
|
+
|
|
14
|
+
export class Service extends DatabaseService<Model> {
|
|
15
|
+
public constructor() {
|
|
16
|
+
super(Model);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@CaptureSpan()
|
|
20
|
+
protected override async onBeforeCreate(
|
|
21
|
+
createBy: CreateBy<Model>,
|
|
22
|
+
): Promise<OnCreate<Model>> {
|
|
23
|
+
/*
|
|
24
|
+
* The attach form submits the project via the `project` relation, so the
|
|
25
|
+
* `projectId` FK is not set yet. Resolve it and persist it (the column is
|
|
26
|
+
* required / NOT NULL) before validating the default teams against it.
|
|
27
|
+
*/
|
|
28
|
+
const projectId: ObjectID | undefined = resolveAttachmentProjectId(
|
|
29
|
+
createBy.data,
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
if (projectId) {
|
|
33
|
+
createBy.data.projectId = projectId;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
await validateGlobalProviderProjectTeams({
|
|
37
|
+
teams: createBy.data.teams,
|
|
38
|
+
projectId,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return { createBy, carryForward: null };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@CaptureSpan()
|
|
45
|
+
protected override async onBeforeUpdate(
|
|
46
|
+
updateBy: UpdateBy<Model>,
|
|
47
|
+
): Promise<OnUpdate<Model>> {
|
|
48
|
+
// `updateBy.data` is a partial-entity shape; narrow the relation/id here.
|
|
49
|
+
const teams: Array<Team> | undefined = updateBy.data.teams as unknown as
|
|
50
|
+
| Array<Team>
|
|
51
|
+
| undefined;
|
|
52
|
+
|
|
53
|
+
if (teams && teams.length > 0) {
|
|
54
|
+
const explicitProjectId: ObjectID | undefined = updateBy.data
|
|
55
|
+
.projectId as unknown as ObjectID | undefined;
|
|
56
|
+
|
|
57
|
+
if (explicitProjectId) {
|
|
58
|
+
await validateGlobalProviderProjectTeams({
|
|
59
|
+
teams,
|
|
60
|
+
projectId: explicitProjectId,
|
|
61
|
+
});
|
|
62
|
+
} else {
|
|
63
|
+
// projectId is immutable here; resolve it from the row(s) being updated.
|
|
64
|
+
const rows: Array<Model> = await this.findBy({
|
|
65
|
+
query: updateBy.query,
|
|
66
|
+
select: { _id: true, projectId: true },
|
|
67
|
+
limit: LIMIT_PER_PROJECT,
|
|
68
|
+
skip: 0,
|
|
69
|
+
props: { isRoot: true },
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
for (const row of rows) {
|
|
73
|
+
await validateGlobalProviderProjectTeams({
|
|
74
|
+
teams,
|
|
75
|
+
projectId: row.projectId,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return { updateBy, carryForward: null };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export default new Service();
|
package/Server/Services/Index.ts
CHANGED
|
@@ -106,6 +106,10 @@ import ProfileSampleService from "./ProfileSampleService";
|
|
|
106
106
|
import ProjectSmtpConfigService from "./ProjectSmtpConfigService";
|
|
107
107
|
import ProjectSsoService from "./ProjectSsoService";
|
|
108
108
|
import ProjectOidcService from "./ProjectOidcService";
|
|
109
|
+
import GlobalSsoService from "./GlobalSsoService";
|
|
110
|
+
import GlobalOidcService from "./GlobalOidcService";
|
|
111
|
+
import GlobalSsoProjectService from "./GlobalSsoProjectService";
|
|
112
|
+
import GlobalOidcProjectService from "./GlobalOidcProjectService";
|
|
109
113
|
import PromoCodeService from "./PromoCodeService";
|
|
110
114
|
import EnterpriseLicenseService from "./EnterpriseLicenseService";
|
|
111
115
|
import OpenSourceDeploymentService from "./OpenSourceDeploymentService";
|
|
@@ -343,6 +347,10 @@ const services: Array<BaseService> = [
|
|
|
343
347
|
AIAgentTaskPullRequestService,
|
|
344
348
|
ProjectSsoService,
|
|
345
349
|
ProjectOidcService,
|
|
350
|
+
GlobalSsoService,
|
|
351
|
+
GlobalOidcService,
|
|
352
|
+
GlobalSsoProjectService,
|
|
353
|
+
GlobalOidcProjectService,
|
|
346
354
|
|
|
347
355
|
ScheduledMaintenanceCustomFieldService,
|
|
348
356
|
ScheduledMaintenanceInternalNoteService,
|
|
@@ -103,6 +103,14 @@ export class ProjectService extends DatabaseService<Model> {
|
|
|
103
103
|
*/
|
|
104
104
|
private requireSsoForLoginCache: InMemoryTTLCache<boolean> =
|
|
105
105
|
new InMemoryTTLCache(10_000);
|
|
106
|
+
/*
|
|
107
|
+
* Caches the `requireSsoWithSsoProviderId` discriminator per project so the
|
|
108
|
+
* enforce-SSO middleware can require that the SSO token was issued by a
|
|
109
|
+
* specific provider. Stored as a string id (or null when unset). Populated
|
|
110
|
+
* alongside `getRequireSsoForLogin` so the common path stays a single query.
|
|
111
|
+
*/
|
|
112
|
+
private requireSsoWithSsoProviderIdCache: InMemoryTTLCache<string | null> =
|
|
113
|
+
new InMemoryTTLCache(10_000);
|
|
106
114
|
/*
|
|
107
115
|
* Caches the current billing plan per project. `getCurrentPlan` is hit
|
|
108
116
|
* by `CommonAPI.getDatabaseCommonInteractionProps` on every
|
|
@@ -351,6 +359,10 @@ export class ProjectService extends DatabaseService<Model> {
|
|
|
351
359
|
this.requireSsoForLoginCache.clear();
|
|
352
360
|
}
|
|
353
361
|
|
|
362
|
+
if (updateBy.data.requireSsoWithSsoProviderId !== undefined) {
|
|
363
|
+
this.requireSsoWithSsoProviderIdCache.clear();
|
|
364
|
+
}
|
|
365
|
+
|
|
354
366
|
if (IsBillingEnabled) {
|
|
355
367
|
if (
|
|
356
368
|
updateBy.data.businessDetails ||
|
|
@@ -1338,7 +1350,7 @@ export class ProjectService extends DatabaseService<Model> {
|
|
|
1338
1350
|
|
|
1339
1351
|
const project: Model | null = await this.findOneById({
|
|
1340
1352
|
id: projectId,
|
|
1341
|
-
select: { requireSsoForLogin: true },
|
|
1353
|
+
select: { requireSsoForLogin: true, requireSsoWithSsoProviderId: true },
|
|
1342
1354
|
props: { isRoot: true },
|
|
1343
1355
|
});
|
|
1344
1356
|
|
|
@@ -1349,9 +1361,40 @@ export class ProjectService extends DatabaseService<Model> {
|
|
|
1349
1361
|
|
|
1350
1362
|
const value: boolean = Boolean(project.requireSsoForLogin);
|
|
1351
1363
|
this.requireSsoForLoginCache.set(key, value, 60_000);
|
|
1364
|
+
this.requireSsoWithSsoProviderIdCache.set(
|
|
1365
|
+
key,
|
|
1366
|
+
project.requireSsoWithSsoProviderId
|
|
1367
|
+
? project.requireSsoWithSsoProviderId.toString()
|
|
1368
|
+
: null,
|
|
1369
|
+
60_000,
|
|
1370
|
+
);
|
|
1352
1371
|
return value;
|
|
1353
1372
|
}
|
|
1354
1373
|
|
|
1374
|
+
/**
|
|
1375
|
+
* Returns the specific SSO provider id a project requires for SSO-enforced
|
|
1376
|
+
* login, or null when any trusted provider is acceptable. Cached for 60s and
|
|
1377
|
+
* populated by `getRequireSsoForLogin`, so the enforce path stays one query.
|
|
1378
|
+
*/
|
|
1379
|
+
@CaptureSpan()
|
|
1380
|
+
public async getRequireSsoWithSsoProviderId(
|
|
1381
|
+
projectId: ObjectID,
|
|
1382
|
+
): Promise<ObjectID | null> {
|
|
1383
|
+
const key: string = projectId.toString();
|
|
1384
|
+
const cached: string | null | undefined =
|
|
1385
|
+
this.requireSsoWithSsoProviderIdCache.get(key);
|
|
1386
|
+
if (cached !== undefined) {
|
|
1387
|
+
return cached ? new ObjectID(cached) : null;
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
// Populate both caches via the existing single-query path.
|
|
1391
|
+
await this.getRequireSsoForLogin(projectId);
|
|
1392
|
+
|
|
1393
|
+
const populated: string | null | undefined =
|
|
1394
|
+
this.requireSsoWithSsoProviderIdCache.get(key);
|
|
1395
|
+
return populated ? new ObjectID(populated) : null;
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1355
1398
|
@CaptureSpan()
|
|
1356
1399
|
public async getOwners(projectId: ObjectID): Promise<Array<User>> {
|
|
1357
1400
|
if (!projectId) {
|
package/Server/Utils/Cookie.ts
CHANGED
|
@@ -8,6 +8,7 @@ import StatusPagePrivateUser from "../../Models/DatabaseModels/StatusPagePrivate
|
|
|
8
8
|
import OneUptimeDate from "../../Types/Date";
|
|
9
9
|
import PositiveNumber from "../../Types/PositiveNumber";
|
|
10
10
|
import CookieName from "../../Types/CookieName";
|
|
11
|
+
import SsoProviderType from "../../Types/SSO/SsoProviderType";
|
|
11
12
|
import {
|
|
12
13
|
MASTER_PASSWORD_COOKIE_IDENTIFIER,
|
|
13
14
|
MASTER_PASSWORD_COOKIE_MAX_AGE_IN_DAYS,
|
|
@@ -38,15 +39,24 @@ export default class CookieUtil {
|
|
|
38
39
|
return cookies;
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
/*
|
|
43
|
+
* Builds a per-project SSO token. The optional `ssoProviderId` /
|
|
44
|
+
* `ssoProviderType` discriminator records WHICH identity provider issued the
|
|
45
|
+
* token, so a project that enforces SSO can require a specific provider
|
|
46
|
+
* (e.g. its own Project SSO, or an instance-wide Global SSO). Used by both
|
|
47
|
+
* the cookie flow (web) and the deep-link flow (mobile) so the token shape
|
|
48
|
+
* stays identical.
|
|
49
|
+
*/
|
|
41
50
|
@CaptureSpan()
|
|
42
|
-
public static
|
|
51
|
+
public static getSSOToken(data: {
|
|
43
52
|
user: User;
|
|
44
53
|
projectId: ObjectID;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
54
|
+
ssoProviderId?: ObjectID | undefined;
|
|
55
|
+
ssoProviderType?: SsoProviderType | undefined;
|
|
56
|
+
}): string {
|
|
57
|
+
const { user, projectId } = data;
|
|
48
58
|
|
|
49
|
-
|
|
59
|
+
return JSONWebToken.sign({
|
|
50
60
|
data: {
|
|
51
61
|
userId: user.id!,
|
|
52
62
|
projectId: projectId,
|
|
@@ -54,9 +64,33 @@ export default class CookieUtil {
|
|
|
54
64
|
email: user.email,
|
|
55
65
|
isMasterAdmin: false,
|
|
56
66
|
isGeneralLogin: false,
|
|
67
|
+
ssoProviderId: data.ssoProviderId
|
|
68
|
+
? data.ssoProviderId.toString()
|
|
69
|
+
: undefined,
|
|
70
|
+
ssoProviderType: data.ssoProviderType
|
|
71
|
+
? data.ssoProviderType.toString()
|
|
72
|
+
: undefined,
|
|
57
73
|
},
|
|
58
74
|
expiresInSeconds: OneUptimeDate.getSecondsInDays(new PositiveNumber(30)),
|
|
59
75
|
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@CaptureSpan()
|
|
79
|
+
public static setSSOCookie(data: {
|
|
80
|
+
user: User;
|
|
81
|
+
projectId: ObjectID;
|
|
82
|
+
expressResponse: ExpressResponse;
|
|
83
|
+
ssoProviderId?: ObjectID | undefined;
|
|
84
|
+
ssoProviderType?: SsoProviderType | undefined;
|
|
85
|
+
}): void {
|
|
86
|
+
const { projectId, expressResponse: res } = data;
|
|
87
|
+
|
|
88
|
+
const ssoToken: string = CookieUtil.getSSOToken({
|
|
89
|
+
user: data.user,
|
|
90
|
+
projectId: projectId,
|
|
91
|
+
ssoProviderId: data.ssoProviderId,
|
|
92
|
+
ssoProviderType: data.ssoProviderType,
|
|
93
|
+
});
|
|
60
94
|
|
|
61
95
|
CookieUtil.setCookie(res, CookieUtil.getUserSSOKey(projectId), ssoToken, {
|
|
62
96
|
maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)),
|
|
@@ -6,6 +6,7 @@ import JSONFunctions from "../../Types/JSONFunctions";
|
|
|
6
6
|
import JSONWebTokenData from "../../Types/JsonWebTokenData";
|
|
7
7
|
import Name from "../../Types/Name";
|
|
8
8
|
import ObjectID from "../../Types/ObjectID";
|
|
9
|
+
import SsoProviderType from "../../Types/SSO/SsoProviderType";
|
|
9
10
|
import Timezone from "../../Types/Timezone";
|
|
10
11
|
import StatusPagePrivateUser from "../../Models/DatabaseModels/StatusPagePrivateUser";
|
|
11
12
|
import User from "../../Models/DatabaseModels/User";
|
|
@@ -139,6 +140,12 @@ class JSONWebToken {
|
|
|
139
140
|
sessionId: decoded["sessionId"]
|
|
140
141
|
? new ObjectID(decoded["sessionId"] as string)
|
|
141
142
|
: undefined,
|
|
143
|
+
ssoProviderId: decoded["ssoProviderId"]
|
|
144
|
+
? new ObjectID(decoded["ssoProviderId"] as string)
|
|
145
|
+
: undefined,
|
|
146
|
+
ssoProviderType: decoded["ssoProviderType"]
|
|
147
|
+
? (decoded["ssoProviderType"] as SsoProviderType)
|
|
148
|
+
: undefined,
|
|
142
149
|
};
|
|
143
150
|
} catch (e) {
|
|
144
151
|
logger.error(e);
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
|
|
2
|
+
import BadDataException from "../../Types/Exception/BadDataException";
|
|
3
|
+
import ObjectID from "../../Types/ObjectID";
|
|
4
|
+
import Project from "../../Models/DatabaseModels/Project";
|
|
5
|
+
import Team from "../../Models/DatabaseModels/Team";
|
|
6
|
+
import QueryHelper from "../Types/Database/QueryHelper";
|
|
7
|
+
import TeamService from "../Services/TeamService";
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
* The Admin Dashboard attach form submits the chosen project via the `project`
|
|
11
|
+
* relation, not the `projectId` FK: the entity dropdown sets the related
|
|
12
|
+
* Project (as a model with only `_id`), so `createBy.data.projectId` is
|
|
13
|
+
* undefined at create-hook time. Resolve the FK from either shape so the hook
|
|
14
|
+
* can populate the NOT NULL `projectId` column and validate teams against it.
|
|
15
|
+
*/
|
|
16
|
+
export const resolveAttachmentProjectId: (data: {
|
|
17
|
+
projectId?: ObjectID | undefined;
|
|
18
|
+
project?: Project | undefined;
|
|
19
|
+
}) => ObjectID | undefined = (data: {
|
|
20
|
+
projectId?: ObjectID | undefined;
|
|
21
|
+
project?: Project | undefined;
|
|
22
|
+
}): ObjectID | undefined => {
|
|
23
|
+
if (data.projectId) {
|
|
24
|
+
return data.projectId;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const project: (Project & { _id?: string }) | undefined = data.project as
|
|
28
|
+
| (Project & { _id?: string })
|
|
29
|
+
| undefined;
|
|
30
|
+
|
|
31
|
+
if (!project) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const idString: string | undefined =
|
|
36
|
+
project._id?.toString() || (project.id ? project.id.toString() : undefined);
|
|
37
|
+
|
|
38
|
+
return idString ? new ObjectID(idString) : undefined;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/*
|
|
42
|
+
* Guards a Global SSO / Global OIDC project-attachment: every default team
|
|
43
|
+
* selected for the attachment MUST belong to the same project the attachment
|
|
44
|
+
* targets.
|
|
45
|
+
*
|
|
46
|
+
* Without this guard, an admin (or a direct API call) could attach a team that
|
|
47
|
+
* lives in project B to an attachment that targets project A. The SSO/OIDC
|
|
48
|
+
* login fan-out (see App/FeatureSet/Identity/API/GlobalSSO.ts) provisions a
|
|
49
|
+
* TeamMember with `projectId = attachment.projectId` but `teamId = team.id`,
|
|
50
|
+
* and because that path runs with `ignoreHooks: true` no service-level
|
|
51
|
+
* validation would catch the mismatch — producing a corrupt, cross-project
|
|
52
|
+
* membership row. This is the server-side backstop for the project-scoped team
|
|
53
|
+
* picker in the Admin Dashboard.
|
|
54
|
+
*/
|
|
55
|
+
type ValidateGlobalProviderProjectTeamsFunction = (data: {
|
|
56
|
+
teams: Array<Team> | undefined;
|
|
57
|
+
projectId: ObjectID | undefined;
|
|
58
|
+
}) => Promise<void>;
|
|
59
|
+
|
|
60
|
+
const validateGlobalProviderProjectTeams: ValidateGlobalProviderProjectTeamsFunction =
|
|
61
|
+
async (data: {
|
|
62
|
+
teams: Array<Team> | undefined;
|
|
63
|
+
projectId: ObjectID | undefined;
|
|
64
|
+
}): Promise<void> => {
|
|
65
|
+
const teams: Array<Team> | undefined = data.teams;
|
|
66
|
+
|
|
67
|
+
if (!teams || teams.length === 0) {
|
|
68
|
+
// No default teams selected: nothing to validate.
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!data.projectId) {
|
|
73
|
+
throw new BadDataException(
|
|
74
|
+
"A project must be selected before choosing default teams.",
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const projectId: string = data.projectId.toString();
|
|
79
|
+
|
|
80
|
+
const teamIds: Array<string> = teams
|
|
81
|
+
.map((team: Team) => {
|
|
82
|
+
return (
|
|
83
|
+
team.id?.toString() ||
|
|
84
|
+
(team as { _id?: string })._id?.toString() ||
|
|
85
|
+
undefined
|
|
86
|
+
);
|
|
87
|
+
})
|
|
88
|
+
.filter((id: string | undefined): id is string => {
|
|
89
|
+
return Boolean(id);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
if (teamIds.length === 0) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const foundTeams: Array<Team> = await TeamService.findBy({
|
|
97
|
+
query: { _id: QueryHelper.any(teamIds) },
|
|
98
|
+
select: { _id: true, projectId: true },
|
|
99
|
+
limit: LIMIT_PER_PROJECT,
|
|
100
|
+
skip: 0,
|
|
101
|
+
props: { isRoot: true },
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
if (foundTeams.length !== teamIds.length) {
|
|
105
|
+
throw new BadDataException(
|
|
106
|
+
"One or more selected teams could not be found.",
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
for (const team of foundTeams) {
|
|
111
|
+
if (team.projectId?.toString() !== projectId) {
|
|
112
|
+
throw new BadDataException(
|
|
113
|
+
"All selected teams must belong to the project this provider is attached to.",
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export default validateGlobalProviderProjectTeams;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import ProjectMiddleware from "../../../Server/Middleware/ProjectAuthorization";
|
|
2
2
|
import UserMiddleware from "../../../Server/Middleware/UserAuthorization";
|
|
3
3
|
import AccessTokenService from "../../../Server/Services/AccessTokenService";
|
|
4
|
+
import GlobalConfigService from "../../../Server/Services/GlobalConfigService";
|
|
4
5
|
import ProjectService from "../../../Server/Services/ProjectService";
|
|
5
6
|
import TeamMemberService from "../../../Server/Services/TeamMemberService";
|
|
6
7
|
import UserService from "../../../Server/Services/UserService";
|
|
@@ -26,7 +27,6 @@ import {
|
|
|
26
27
|
UserGlobalAccessPermission,
|
|
27
28
|
UserTenantAccessPermission,
|
|
28
29
|
} from "../../../Types/Permission";
|
|
29
|
-
import Project from "../../../Models/DatabaseModels/Project";
|
|
30
30
|
import {
|
|
31
31
|
describe,
|
|
32
32
|
expect,
|
|
@@ -44,6 +44,7 @@ jest.mock("../../../Server/Middleware/ProjectAuthorization");
|
|
|
44
44
|
jest.mock("../../../Server/Utils/JsonWebToken");
|
|
45
45
|
jest.mock("../../../Server/Services/UserService");
|
|
46
46
|
jest.mock("../../../Server/Services/AccessTokenService");
|
|
47
|
+
jest.mock("../../../Server/Services/GlobalConfigService");
|
|
47
48
|
jest.mock("../../../Server/Utils/Response");
|
|
48
49
|
jest.mock("../../../Server/Services/ProjectService");
|
|
49
50
|
jest.mock("../../../Server/Services/TeamMemberService");
|
|
@@ -56,7 +57,6 @@ describe("UserMiddleware", () => {
|
|
|
56
57
|
const mockedAccessToken: string = ObjectID.generate().toString();
|
|
57
58
|
const projectId: ObjectID = ObjectID.generate();
|
|
58
59
|
const userId: ObjectID = ObjectID.generate();
|
|
59
|
-
const mockedProject: Project = { _id: projectId.toString() } as Project;
|
|
60
60
|
|
|
61
61
|
beforeEach(() => {
|
|
62
62
|
jest.clearAllMocks();
|
|
@@ -590,15 +590,32 @@ describe("UserMiddleware", () => {
|
|
|
590
590
|
ProjectService,
|
|
591
591
|
"getRequireSsoForLogin",
|
|
592
592
|
);
|
|
593
|
+
const spyGetRequireSsoWithSsoProviderId: jest.SpyInstance = getJestSpyOn(
|
|
594
|
+
ProjectService,
|
|
595
|
+
"getRequireSsoWithSsoProviderId",
|
|
596
|
+
);
|
|
593
597
|
const spyDoesSsoTokenForProjectExist: jest.SpyInstance = getJestSpyOn(
|
|
594
598
|
UserMiddleware,
|
|
595
599
|
"doesSsoTokenForProjectExist",
|
|
596
600
|
);
|
|
601
|
+
const spyGetGlobalRequireSsoForLogin: jest.SpyInstance = getJestSpyOn(
|
|
602
|
+
GlobalConfigService,
|
|
603
|
+
"getRequireSsoForLogin",
|
|
604
|
+
);
|
|
597
605
|
|
|
598
606
|
afterEach(() => {
|
|
599
607
|
jest.clearAllMocks();
|
|
600
608
|
});
|
|
601
609
|
|
|
610
|
+
/*
|
|
611
|
+
* By default no project requires a specific SSO provider (discriminator),
|
|
612
|
+
* and the instance-wide "Require SSO for Login" flag is off.
|
|
613
|
+
*/
|
|
614
|
+
beforeEach(() => {
|
|
615
|
+
spyGetRequireSsoWithSsoProviderId.mockResolvedValue(null);
|
|
616
|
+
spyGetGlobalRequireSsoForLogin.mockResolvedValue(false);
|
|
617
|
+
});
|
|
618
|
+
|
|
602
619
|
test("should throw 'Invalid tenantId' error, when project is not found for the tenantId", async () => {
|
|
603
620
|
spyGetRequireSsoForLogin.mockRejectedValueOnce(
|
|
604
621
|
new BadDataException("Project not found"),
|
|
@@ -630,6 +647,7 @@ describe("UserMiddleware", () => {
|
|
|
630
647
|
req,
|
|
631
648
|
projectId,
|
|
632
649
|
userId,
|
|
650
|
+
undefined,
|
|
633
651
|
);
|
|
634
652
|
});
|
|
635
653
|
|
|
@@ -688,16 +706,37 @@ describe("UserMiddleware", () => {
|
|
|
688
706
|
projectId,
|
|
689
707
|
} as UserTenantAccessPermission;
|
|
690
708
|
|
|
691
|
-
const
|
|
709
|
+
const spyGetProjectRequireSsoForLogin: jest.SpyInstance = getJestSpyOn(
|
|
710
|
+
ProjectService,
|
|
711
|
+
"getRequireSsoForLogin",
|
|
712
|
+
);
|
|
713
|
+
const spyGetRequireSsoWithSsoProviderId: jest.SpyInstance = getJestSpyOn(
|
|
714
|
+
ProjectService,
|
|
715
|
+
"getRequireSsoWithSsoProviderId",
|
|
716
|
+
);
|
|
692
717
|
const spyDoesSsoTokenForProjectExist: jest.SpyInstance = getJestSpyOn(
|
|
693
718
|
UserMiddleware,
|
|
694
719
|
"doesSsoTokenForProjectExist",
|
|
695
720
|
);
|
|
721
|
+
const spyGetGlobalRequireSsoForLogin: jest.SpyInstance = getJestSpyOn(
|
|
722
|
+
GlobalConfigService,
|
|
723
|
+
"getRequireSsoForLogin",
|
|
724
|
+
);
|
|
696
725
|
|
|
697
726
|
afterEach(() => {
|
|
698
727
|
jest.clearAllMocks();
|
|
699
728
|
});
|
|
700
729
|
|
|
730
|
+
/*
|
|
731
|
+
* By default neither a project's own nor the instance-wide "Require SSO for
|
|
732
|
+
* Login" flag is on, and no project requires a specific SSO provider.
|
|
733
|
+
*/
|
|
734
|
+
beforeEach(() => {
|
|
735
|
+
spyGetProjectRequireSsoForLogin.mockResolvedValue(false);
|
|
736
|
+
spyGetRequireSsoWithSsoProviderId.mockResolvedValue(null);
|
|
737
|
+
spyGetGlobalRequireSsoForLogin.mockResolvedValue(false);
|
|
738
|
+
});
|
|
739
|
+
|
|
701
740
|
test("should return null, when projectIds length is zero", async () => {
|
|
702
741
|
const result: Dictionary<UserTenantAccessPermission> | null =
|
|
703
742
|
await UserMiddleware.getUserTenantAccessPermissionForMultiTenant(
|
|
@@ -707,14 +746,12 @@ describe("UserMiddleware", () => {
|
|
|
707
746
|
);
|
|
708
747
|
|
|
709
748
|
expect(result).toBeNull();
|
|
710
|
-
expect(
|
|
749
|
+
expect(spyGetProjectRequireSsoForLogin).not.toBeCalled();
|
|
711
750
|
});
|
|
712
751
|
|
|
713
752
|
test("should return default tenant access permission, when project for a projectId is found, sso is required for login, but sso token does not exist for that projectId", async () => {
|
|
714
753
|
spyDoesSsoTokenForProjectExist.mockReturnValueOnce(false);
|
|
715
|
-
|
|
716
|
-
{ ...mockedProject, requireSsoForLogin: true },
|
|
717
|
-
]);
|
|
754
|
+
spyGetProjectRequireSsoForLogin.mockResolvedValueOnce(true);
|
|
718
755
|
|
|
719
756
|
const spyGetDefaultUserTenantAccessPermission: jest.SpyInstance =
|
|
720
757
|
getJestSpyOn(
|
|
@@ -736,6 +773,7 @@ describe("UserMiddleware", () => {
|
|
|
736
773
|
req,
|
|
737
774
|
projectId,
|
|
738
775
|
userId,
|
|
776
|
+
undefined,
|
|
739
777
|
);
|
|
740
778
|
expect(spyGetDefaultUserTenantAccessPermission).toHaveBeenCalledWith(
|
|
741
779
|
projectId,
|
|
@@ -743,8 +781,6 @@ describe("UserMiddleware", () => {
|
|
|
743
781
|
});
|
|
744
782
|
|
|
745
783
|
test("should return user tenant access permission, when project for a projectId is found, sso is not required for login and project level permission exist for the projectId", async () => {
|
|
746
|
-
spyFindBy.mockResolvedValueOnce([mockedProject]);
|
|
747
|
-
|
|
748
784
|
const spyGetUserTenantAccessPermission: jest.SpyInstance = getJestSpyOn(
|
|
749
785
|
AccessTokenService,
|
|
750
786
|
"getUserTenantAccessPermission",
|
|
@@ -768,8 +804,6 @@ describe("UserMiddleware", () => {
|
|
|
768
804
|
});
|
|
769
805
|
|
|
770
806
|
test("should return null, when project for a projectId is found, sso is not required for login but project level permission does not exist for the projectId", async () => {
|
|
771
|
-
spyFindBy.mockResolvedValueOnce([mockedProject]);
|
|
772
|
-
|
|
773
807
|
const spyGetUserTenantAccessPermission: jest.SpyInstance = getJestSpyOn(
|
|
774
808
|
AccessTokenService,
|
|
775
809
|
"getUserTenantAccessPermission",
|
|
@@ -790,7 +824,9 @@ describe("UserMiddleware", () => {
|
|
|
790
824
|
});
|
|
791
825
|
|
|
792
826
|
test("should return user tenant access permission, when project for a projectId is not found, but project level permission exist for the projectId", async () => {
|
|
793
|
-
|
|
827
|
+
spyGetProjectRequireSsoForLogin.mockRejectedValueOnce(
|
|
828
|
+
new BadDataException("Project not found"),
|
|
829
|
+
);
|
|
794
830
|
|
|
795
831
|
getJestSpyOn(
|
|
796
832
|
AccessTokenService,
|
|
@@ -810,7 +846,9 @@ describe("UserMiddleware", () => {
|
|
|
810
846
|
});
|
|
811
847
|
|
|
812
848
|
test("should return null, when project for a projectId is not found, and project level permission does not exist for the projectId", async () => {
|
|
813
|
-
|
|
849
|
+
spyGetProjectRequireSsoForLogin.mockRejectedValueOnce(
|
|
850
|
+
new BadDataException("Project not found"),
|
|
851
|
+
);
|
|
814
852
|
|
|
815
853
|
const spyGetUserTenantAccessPermission: jest.SpyInstance = getJestSpyOn(
|
|
816
854
|
AccessTokenService,
|