@oneuptime/common 12.0.14 → 12.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/GlobalOidc.ts +20 -0
- package/Models/DatabaseModels/GlobalSso.ts +20 -0
- package/Models/DatabaseModels/MarketingConversion.ts +19 -9
- package/Models/DatabaseModels/NetworkDeviceLinkRule.ts +46 -0
- package/Models/DatabaseModels/StatusPage.ts +7 -4
- package/Server/API/StatusPageAPI.ts +29 -4
- package/Server/EnvironmentConfig.ts +7 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787800000000-AddScopeToNetworkDeviceLinkRule.ts +19 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787900000000-AddRestrictToAttachedProjectsToGlobalSso.ts +40 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Middleware/SlackAuthorization.ts +43 -6
- package/Server/Middleware/UserAuthorization.ts +236 -26
- package/Server/Middleware/WhatsAppAuthorization.ts +43 -3
- package/Server/Services/GlobalOidcProjectService.ts +126 -1
- package/Server/Services/GlobalOidcService.ts +132 -0
- package/Server/Services/GlobalSsoProjectService.ts +126 -1
- package/Server/Services/GlobalSsoService.ts +132 -0
- package/Server/Services/UserEmailService.ts +4 -4
- package/Server/Utils/CronTab.ts +73 -0
- package/Server/Utils/GlobalSsoAuthorization.ts +175 -0
- package/Server/Utils/Marketing/ConversionUploadProvider.ts +35 -5
- package/Server/Utils/Marketing/Providers/GoogleAds.ts +1 -1
- package/Server/Utils/Marketing/Providers/LinkedIn.ts +1 -1
- package/Server/Utils/Marketing/Providers/Meta.ts +1 -1
- package/Server/Utils/Marketing/Providers/MicrosoftAds.ts +1 -1
- package/Server/Utils/Marketing/Providers/Reddit.ts +1 -1
- package/Server/Utils/Monitor/Criteria/APIRequestCriteria.ts +48 -33
- package/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.ts +35 -30
- package/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.ts +33 -28
- package/Server/Utils/Monitor/Criteria/EvaluateOverTime.ts +475 -47
- package/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.ts +35 -30
- package/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.ts +34 -28
- package/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.ts +33 -28
- package/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.ts +69 -40
- package/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.ts +35 -29
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +8 -0
- package/Server/Utils/Monitor/MonitorResource.ts +161 -21
- package/Server/Utils/StatusPageContentSecurityPolicy.ts +56 -0
- package/Server/Utils/StatusPageCustomizationAccess.ts +117 -0
- package/Tests/Server/API/StatusPageCustomizationOrigin.test.ts +348 -0
- package/Tests/Server/Infrastructure/Postgres/FixTotpOtpUrlAlgorithmMigration.test.ts +9 -59
- package/Tests/Server/Infrastructure/Postgres/SchemaMigrationsOrdering.test.ts +227 -0
- package/Tests/Server/Middleware/SlackAuthorization.test.ts +149 -0
- package/Tests/Server/Middleware/UserAuthorization.test.ts +27 -13
- package/Tests/Server/Middleware/UserAuthorizationGlobalSsoGovernance.test.ts +1455 -0
- package/Tests/Server/Middleware/UserAuthorizationSSOProvider.test.ts +91 -2
- package/Tests/Server/Middleware/WhatsAppAuthorization.test.ts +233 -0
- package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +21 -8
- package/Tests/Server/Services/GlobalSsoProviderAuthorization.test.ts +1242 -0
- package/Tests/Server/Services/MonitorResourceProbeAgreementHydration.test.ts +459 -0
- package/Tests/Server/Services/MonitorResourceProbeAgreementReuse.test.ts +10 -0
- package/Tests/Server/Types/AnalyticsDatabase/AggregateUtilBucketTimestamp.test.ts +416 -0
- package/Tests/Server/Types/Database/QueryHelperManyToManyAndEmptyValues.test.ts +551 -0
- package/Tests/Server/Types/Database/SelectUtil.test.ts +348 -0
- package/Tests/Server/Utils/CronTab.test.ts +116 -0
- package/Tests/Server/Utils/GlobalSSOToken.test.ts +581 -0
- package/Tests/Server/Utils/GlobalSsoAuthorization.test.ts +1401 -0
- package/Tests/Server/Utils/Marketing/AdUploadableConversionTypes.test.ts +254 -0
- package/Tests/Server/Utils/Marketing/ConversionUploadProvider.test.ts +1 -1
- package/Tests/Server/Utils/Monitor/Criteria/APIRequestCriteriaEvaluateOverTime.test.ts +508 -0
- package/Tests/Server/Utils/Monitor/Criteria/APIRequestCriteriaPortTimings.test.ts +30 -9
- package/Tests/Server/Utils/Monitor/Criteria/EvaluateOverTime.test.ts +987 -0
- package/Tests/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.test.ts +916 -0
- package/Tests/Server/Utils/Monitor/Criteria/ProfileMonitorCriteria.test.ts +382 -0
- package/Tests/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.test.ts +1219 -0
- package/Tests/Server/Utils/Monitor/MonitorResourceIngestedStepSelection.test.ts +86 -0
- package/Tests/Server/Utils/StatusPageContentSecurityPolicy.test.ts +136 -0
- package/Tests/Server/Utils/StatusPageCustomizationAccess.test.ts +223 -0
- package/Tests/Types/Monitor/MonitorStepConfigHelpers.test.ts +165 -0
- package/Tests/Types/NetworkDevice/NetworkDeviceLinkRuleScope.test.ts +215 -0
- package/Tests/UI/Components/Forms/FormSubmitAnalytics.test.tsx +206 -0
- package/Tests/UI/Utils/AIChatExport/ConversationMarkdown.test.ts +774 -0
- package/Tests/UI/Utils/AIChatExport/MarkdownBlocks.test.ts +791 -0
- package/Tests/UI/Utils/AIChatExport/MarkdownSafety.test.ts +544 -0
- package/Tests/Utils/Analytics.test.ts +187 -0
- package/Tests/Utils/Monitor/MonitorMetricType.test.ts +80 -0
- package/Tests/Utils/Monitor/NetworkDeviceLinkRuleUtil.test.ts +1291 -0
- package/Tests/Utils/NetworkDiscovery/UnclaimedScanDiagnosis.test.ts +214 -0
- package/Types/Marketing/MarketingConversion.ts +12 -0
- package/Types/Monitor/CriteriaFilter.ts +9 -0
- package/Types/NetworkDevice/NetworkDeviceLinkRuleScope.ts +51 -0
- package/UI/Components/FormModal/BasicFormModal.tsx +12 -0
- package/UI/Components/Forms/BasicForm.tsx +13 -1
- package/UI/Components/Forms/BasicModelForm.tsx +2 -1
- package/UI/Components/Forms/ModelForm.tsx +10 -1
- package/UI/Components/Forms/Utils/FormAnalyticsName.ts +39 -0
- package/UI/Components/ModelFormModal/ModelFormModal.tsx +6 -1
- package/Utils/Analytics.ts +55 -2
- package/Utils/Monitor/MonitorMetricType.ts +40 -0
- package/Utils/Monitor/NetworkDeviceLinkRuleUtil.ts +587 -37
- package/Utils/NetworkDiscovery/UnclaimedScanDiagnosis.ts +115 -0
- package/build/dist/Models/DatabaseModels/GlobalOidc.js +21 -0
- package/build/dist/Models/DatabaseModels/GlobalOidc.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalSso.js +21 -0
- package/build/dist/Models/DatabaseModels/GlobalSso.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MarketingConversion.js +20 -10
- package/build/dist/Models/DatabaseModels/MarketingConversion.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceLinkRule.js +47 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceLinkRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +4 -4
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +25 -4
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +6 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787800000000-AddScopeToNetworkDeviceLinkRule.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787800000000-AddScopeToNetworkDeviceLinkRule.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787900000000-AddRestrictToAttachedProjectsToGlobalSso.js +29 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787900000000-AddRestrictToAttachedProjectsToGlobalSso.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/SlackAuthorization.js +28 -4
- package/build/dist/Server/Middleware/SlackAuthorization.js.map +1 -1
- package/build/dist/Server/Middleware/UserAuthorization.js +197 -22
- package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
- package/build/dist/Server/Middleware/WhatsAppAuthorization.js +32 -2
- package/build/dist/Server/Middleware/WhatsAppAuthorization.js.map +1 -1
- package/build/dist/Server/Services/GlobalOidcProjectService.js +105 -0
- package/build/dist/Server/Services/GlobalOidcProjectService.js.map +1 -1
- package/build/dist/Server/Services/GlobalOidcService.js +127 -0
- package/build/dist/Server/Services/GlobalOidcService.js.map +1 -1
- package/build/dist/Server/Services/GlobalSsoProjectService.js +105 -0
- package/build/dist/Server/Services/GlobalSsoProjectService.js.map +1 -1
- package/build/dist/Server/Services/GlobalSsoService.js +127 -0
- package/build/dist/Server/Services/GlobalSsoService.js.map +1 -1
- package/build/dist/Server/Services/UserEmailService.js +4 -4
- package/build/dist/Server/Utils/CronTab.js +66 -0
- package/build/dist/Server/Utils/CronTab.js.map +1 -1
- package/build/dist/Server/Utils/GlobalSsoAuthorization.js +112 -0
- package/build/dist/Server/Utils/GlobalSsoAuthorization.js.map +1 -0
- package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js +17 -1
- package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/Meta.js +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/Meta.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/Reddit.js +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/Reddit.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js +37 -35
- package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.js +21 -25
- package/build/dist/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js +20 -22
- package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js +370 -42
- package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.js +21 -25
- package/build/dist/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js +21 -25
- package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js +21 -25
- package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.js +64 -52
- package/build/dist/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js +25 -29
- package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +8 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +123 -23
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/StatusPageContentSecurityPolicy.js +39 -0
- package/build/dist/Server/Utils/StatusPageContentSecurityPolicy.js.map +1 -0
- package/build/dist/Server/Utils/StatusPageCustomizationAccess.js +85 -0
- package/build/dist/Server/Utils/StatusPageCustomizationAccess.js.map +1 -0
- package/build/dist/Types/Marketing/MarketingConversion.js +10 -0
- package/build/dist/Types/Marketing/MarketingConversion.js.map +1 -1
- package/build/dist/Types/Monitor/CriteriaFilter.js +1 -0
- package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
- package/build/dist/Types/NetworkDevice/NetworkDeviceLinkRuleScope.js +46 -0
- package/build/dist/Types/NetworkDevice/NetworkDeviceLinkRuleScope.js.map +1 -0
- package/build/dist/UI/Components/FormModal/BasicFormModal.js +2 -1
- package/build/dist/UI/Components/FormModal/BasicFormModal.js.map +1 -1
- package/build/dist/UI/Components/Forms/BasicForm.js +10 -1
- package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
- package/build/dist/UI/Components/Forms/BasicModelForm.js +2 -1
- package/build/dist/UI/Components/Forms/BasicModelForm.js.map +1 -1
- package/build/dist/UI/Components/Forms/ModelForm.js +4 -1
- package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
- package/build/dist/UI/Components/Forms/Utils/FormAnalyticsName.js +33 -0
- package/build/dist/UI/Components/Forms/Utils/FormAnalyticsName.js.map +1 -0
- package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js +2 -1
- package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
- package/build/dist/Utils/Analytics.js +50 -2
- package/build/dist/Utils/Analytics.js.map +1 -1
- package/build/dist/Utils/Monitor/MonitorMetricType.js +37 -0
- package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js +318 -37
- package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js.map +1 -1
- package/build/dist/Utils/NetworkDiscovery/UnclaimedScanDiagnosis.js +87 -0
- package/build/dist/Utils/NetworkDiscovery/UnclaimedScanDiagnosis.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,581 @@
|
|
|
1
|
+
import UserMiddleware from "../../../Server/Middleware/UserAuthorization";
|
|
2
|
+
import { EncryptionSecret } from "../../../Server/EnvironmentConfig";
|
|
3
|
+
import CookieUtil from "../../../Server/Utils/Cookie";
|
|
4
|
+
import { ExpressRequest, ExpressResponse } from "../../../Server/Utils/Express";
|
|
5
|
+
import JSONWebToken from "../../../Server/Utils/JsonWebToken";
|
|
6
|
+
import CookieName from "../../../Types/CookieName";
|
|
7
|
+
import Email from "../../../Types/Email";
|
|
8
|
+
import { JSONObject } from "../../../Types/JSON";
|
|
9
|
+
import JSONWebTokenData from "../../../Types/JsonWebTokenData";
|
|
10
|
+
import Name from "../../../Types/Name";
|
|
11
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
12
|
+
import SsoProviderType from "../../../Types/SSO/SsoProviderType";
|
|
13
|
+
import User from "../../../Models/DatabaseModels/User";
|
|
14
|
+
import { describe, expect, jest, test } from "@jest/globals";
|
|
15
|
+
import jwt from "jsonwebtoken";
|
|
16
|
+
|
|
17
|
+
jest.mock("../../../Server/Utils/Logger");
|
|
18
|
+
|
|
19
|
+
/*
|
|
20
|
+
* The Global SSO token is the credential a Global (instance-wide) SAML/OIDC
|
|
21
|
+
* login mints. Unlike the per-project `sso-<projectId>` token it carries NO
|
|
22
|
+
* project binding, which is exactly what lets one token satisfy SSO
|
|
23
|
+
* enforcement for every project the user belongs to.
|
|
24
|
+
*
|
|
25
|
+
* These tests run REAL JWT signing and verification (no mocks). The JWT secret
|
|
26
|
+
* falls back to EncryptionSecret = "secret" when ENCRYPTION_SECRET is unset
|
|
27
|
+
* (see Common/Server/EnvironmentConfig.ts), so no env setup is required.
|
|
28
|
+
*
|
|
29
|
+
* The happy path of UserMiddleware.doesSsoTokenForProjectExist accepting a
|
|
30
|
+
* global token from the `global-sso-token` cookie and from the
|
|
31
|
+
* `x-global-sso-token` header already lives in
|
|
32
|
+
* Common/Tests/Server/Middleware/UserAuthorizationSSOProvider.test.ts and is
|
|
33
|
+
* deliberately not repeated here. What follows is the payload/TTL contract and
|
|
34
|
+
* the rejection paths around it.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
const THIRTY_DAYS_IN_SECONDS: number = 30 * 24 * 60 * 60;
|
|
38
|
+
const THIRTY_DAYS_IN_MILLISECONDS: number = THIRTY_DAYS_IN_SECONDS * 1000;
|
|
39
|
+
|
|
40
|
+
const buildUser: (userId: ObjectID) => User = (userId: ObjectID): User => {
|
|
41
|
+
const user: User = new User();
|
|
42
|
+
user.id = userId;
|
|
43
|
+
user.name = new Name("Global SSO User");
|
|
44
|
+
user.email = new Email("global-sso@oneuptime.com");
|
|
45
|
+
return user;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const buildCookieRequest: (token: string) => ExpressRequest = (
|
|
49
|
+
token: string,
|
|
50
|
+
): ExpressRequest => {
|
|
51
|
+
return {
|
|
52
|
+
cookies: { [CookieUtil.getGlobalSSOKey()]: token },
|
|
53
|
+
headers: {},
|
|
54
|
+
} as unknown as ExpressRequest;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const buildHeaderRequest: (token: string) => ExpressRequest = (
|
|
58
|
+
token: string,
|
|
59
|
+
): ExpressRequest => {
|
|
60
|
+
return {
|
|
61
|
+
cookies: {},
|
|
62
|
+
headers: { "x-global-sso-token": token },
|
|
63
|
+
} as unknown as ExpressRequest;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/*
|
|
67
|
+
* Re-encodes a JWT's payload while keeping the ORIGINAL signature, which is
|
|
68
|
+
* what an attacker editing a token in transit (or in their own browser) would
|
|
69
|
+
* produce. The signature no longer covers the payload, so verification must
|
|
70
|
+
* fail.
|
|
71
|
+
*/
|
|
72
|
+
const tamperWithPayload: (
|
|
73
|
+
token: string,
|
|
74
|
+
mutate: (payload: JSONObject) => void,
|
|
75
|
+
) => string = (
|
|
76
|
+
token: string,
|
|
77
|
+
mutate: (payload: JSONObject) => void,
|
|
78
|
+
): string => {
|
|
79
|
+
const parts: Array<string> = token.split(".");
|
|
80
|
+
|
|
81
|
+
const payload: JSONObject = JSON.parse(
|
|
82
|
+
Buffer.from(parts[1] as string, "base64url").toString("utf8"),
|
|
83
|
+
) as JSONObject;
|
|
84
|
+
|
|
85
|
+
mutate(payload);
|
|
86
|
+
|
|
87
|
+
const rewrittenPayload: string = Buffer.from(
|
|
88
|
+
JSON.stringify(payload),
|
|
89
|
+
"utf8",
|
|
90
|
+
).toString("base64url");
|
|
91
|
+
|
|
92
|
+
return `${parts[0]}.${rewrittenPayload}.${parts[2]}`;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
describe("CookieUtil.getGlobalSSOToken - payload", () => {
|
|
96
|
+
test("pins every claim the global token carries", () => {
|
|
97
|
+
const userId: ObjectID = ObjectID.generate();
|
|
98
|
+
const ssoProviderId: ObjectID = ObjectID.generate();
|
|
99
|
+
const user: User = buildUser(userId);
|
|
100
|
+
|
|
101
|
+
const token: string = CookieUtil.getGlobalSSOToken({
|
|
102
|
+
user,
|
|
103
|
+
ssoProviderId,
|
|
104
|
+
ssoProviderType: SsoProviderType.GlobalSSO,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const payload: JSONObject = JSONWebToken.decodeJsonPayload(token);
|
|
108
|
+
|
|
109
|
+
expect(payload["userId"]).toBe(userId.toString());
|
|
110
|
+
expect(payload["name"]).toBe("Global SSO User");
|
|
111
|
+
expect(payload["email"]).toBe("global-sso@oneuptime.com");
|
|
112
|
+
expect(payload["isMasterAdmin"]).toBe(false);
|
|
113
|
+
expect(payload["isGeneralLogin"]).toBe(false);
|
|
114
|
+
expect(payload["ssoProviderId"]).toBe(ssoProviderId.toString());
|
|
115
|
+
expect(payload["ssoProviderType"]).toBe(SsoProviderType.GlobalSSO);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("carries NO projectId binding - that is what makes it global", () => {
|
|
119
|
+
const userId: ObjectID = ObjectID.generate();
|
|
120
|
+
const user: User = buildUser(userId);
|
|
121
|
+
|
|
122
|
+
const token: string = CookieUtil.getGlobalSSOToken({
|
|
123
|
+
user,
|
|
124
|
+
ssoProviderId: ObjectID.generate(),
|
|
125
|
+
ssoProviderType: SsoProviderType.GlobalOIDC,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
/*
|
|
129
|
+
* JSONWebToken.sign normalises an absent projectId to an empty string, so
|
|
130
|
+
* assert on "no usable project id" rather than on strict absence. Either
|
|
131
|
+
* way there is nothing on the wire that could scope this token to a
|
|
132
|
+
* project.
|
|
133
|
+
*/
|
|
134
|
+
const payload: JSONObject = JSONWebToken.decodeJsonPayload(token);
|
|
135
|
+
expect(payload["projectId"] || undefined).toBeUndefined();
|
|
136
|
+
|
|
137
|
+
// And the decoded view the middleware actually consumes has no project.
|
|
138
|
+
const decoded: JSONWebTokenData = JSONWebToken.decode(token);
|
|
139
|
+
expect(decoded.projectId).toBeUndefined();
|
|
140
|
+
|
|
141
|
+
// Contrast: a per-project token DOES bind a project.
|
|
142
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
143
|
+
const projectToken: string = CookieUtil.getSSOToken({
|
|
144
|
+
user,
|
|
145
|
+
projectId,
|
|
146
|
+
ssoProviderId: ObjectID.generate(),
|
|
147
|
+
ssoProviderType: SsoProviderType.ProjectSSO,
|
|
148
|
+
});
|
|
149
|
+
expect(JSONWebToken.decode(projectToken).projectId?.toString()).toBe(
|
|
150
|
+
projectId.toString(),
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("both Global provider types round-trip through the token", () => {
|
|
155
|
+
const userId: ObjectID = ObjectID.generate();
|
|
156
|
+
const user: User = buildUser(userId);
|
|
157
|
+
|
|
158
|
+
const globalProviderTypes: Array<SsoProviderType> = [
|
|
159
|
+
SsoProviderType.GlobalSSO,
|
|
160
|
+
SsoProviderType.GlobalOIDC,
|
|
161
|
+
];
|
|
162
|
+
|
|
163
|
+
for (const providerType of globalProviderTypes) {
|
|
164
|
+
const ssoProviderId: ObjectID = ObjectID.generate();
|
|
165
|
+
|
|
166
|
+
const token: string = CookieUtil.getGlobalSSOToken({
|
|
167
|
+
user,
|
|
168
|
+
ssoProviderId,
|
|
169
|
+
ssoProviderType: providerType,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const decoded: JSONWebTokenData = JSONWebToken.decode(token);
|
|
173
|
+
|
|
174
|
+
expect(decoded.ssoProviderType).toBe(providerType);
|
|
175
|
+
expect(decoded.ssoProviderId?.toString()).toBe(ssoProviderId.toString());
|
|
176
|
+
expect(decoded.userId.toString()).toBe(userId.toString());
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
/*
|
|
182
|
+
* The mobile app decodes the `exp` claim of exactly this token
|
|
183
|
+
* (MobileApp/src/utils/jwt.ts) to decide when to re-authenticate, so the TTL is
|
|
184
|
+
* part of the contract and not just a server-side detail.
|
|
185
|
+
*/
|
|
186
|
+
describe("CookieUtil.getGlobalSSOToken - 30 day TTL", () => {
|
|
187
|
+
test("exp is 30 days after iat", () => {
|
|
188
|
+
const token: string = CookieUtil.getGlobalSSOToken({
|
|
189
|
+
user: buildUser(ObjectID.generate()),
|
|
190
|
+
ssoProviderId: ObjectID.generate(),
|
|
191
|
+
ssoProviderType: SsoProviderType.GlobalSSO,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const payload: JSONObject = JSONWebToken.decodeJsonPayload(token);
|
|
195
|
+
|
|
196
|
+
expect(typeof payload["iat"]).toBe("number");
|
|
197
|
+
expect(typeof payload["exp"]).toBe("number");
|
|
198
|
+
expect((payload["exp"] as number) - (payload["iat"] as number)).toBe(
|
|
199
|
+
THIRTY_DAYS_IN_SECONDS,
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("exp is ~30 days out from wall-clock now (within tolerance)", () => {
|
|
204
|
+
const nowInSeconds: number = Math.floor(Date.now() / 1000);
|
|
205
|
+
|
|
206
|
+
const token: string = CookieUtil.getGlobalSSOToken({
|
|
207
|
+
user: buildUser(ObjectID.generate()),
|
|
208
|
+
ssoProviderId: ObjectID.generate(),
|
|
209
|
+
ssoProviderType: SsoProviderType.GlobalOIDC,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const payload: JSONObject = JSONWebToken.decodeJsonPayload(token);
|
|
213
|
+
const expiresAt: number = payload["exp"] as number;
|
|
214
|
+
|
|
215
|
+
const toleranceInSeconds: number = 60;
|
|
216
|
+
|
|
217
|
+
expect(expiresAt).toBeGreaterThanOrEqual(
|
|
218
|
+
nowInSeconds + THIRTY_DAYS_IN_SECONDS - toleranceInSeconds,
|
|
219
|
+
);
|
|
220
|
+
expect(expiresAt).toBeLessThanOrEqual(
|
|
221
|
+
nowInSeconds + THIRTY_DAYS_IN_SECONDS + toleranceInSeconds,
|
|
222
|
+
);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe("CookieUtil.setGlobalSSOCookie", () => {
|
|
227
|
+
test("writes the token under CookieName.GlobalSSOToken, httpOnly, 30 day maxAge in ms", () => {
|
|
228
|
+
const userId: ObjectID = ObjectID.generate();
|
|
229
|
+
const ssoProviderId: ObjectID = ObjectID.generate();
|
|
230
|
+
|
|
231
|
+
const cookieCalls: Array<Array<unknown>> = [];
|
|
232
|
+
const res: ExpressResponse = {
|
|
233
|
+
cookie: (...args: Array<unknown>): void => {
|
|
234
|
+
cookieCalls.push(args);
|
|
235
|
+
},
|
|
236
|
+
} as unknown as ExpressResponse;
|
|
237
|
+
|
|
238
|
+
CookieUtil.setGlobalSSOCookie({
|
|
239
|
+
user: buildUser(userId),
|
|
240
|
+
expressResponse: res,
|
|
241
|
+
ssoProviderId,
|
|
242
|
+
ssoProviderType: SsoProviderType.GlobalSSO,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
expect(cookieCalls.length).toBe(1);
|
|
246
|
+
|
|
247
|
+
const callArgs: Array<unknown> = cookieCalls[0] as Array<unknown>;
|
|
248
|
+
|
|
249
|
+
expect(callArgs[0]).toBe(CookieName.GlobalSSOToken);
|
|
250
|
+
expect(callArgs[0]).toBe("global-sso-token");
|
|
251
|
+
|
|
252
|
+
expect(callArgs[2]).toEqual({
|
|
253
|
+
path: "/",
|
|
254
|
+
sameSite: "lax",
|
|
255
|
+
httpOnly: true,
|
|
256
|
+
maxAge: THIRTY_DAYS_IN_MILLISECONDS,
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// The value written is a real, verifiable global token for this user.
|
|
260
|
+
const decoded: JSONWebTokenData = JSONWebToken.decode(
|
|
261
|
+
callArgs[1] as string,
|
|
262
|
+
);
|
|
263
|
+
expect(decoded.userId.toString()).toBe(userId.toString());
|
|
264
|
+
expect(decoded.ssoProviderId?.toString()).toBe(ssoProviderId.toString());
|
|
265
|
+
expect(decoded.ssoProviderType).toBe(SsoProviderType.GlobalSSO);
|
|
266
|
+
expect(decoded.projectId).toBeUndefined();
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test("the global cookie name does not collide with the per-project sso- prefix", () => {
|
|
270
|
+
/*
|
|
271
|
+
* getSsoTokens() keys every cookie starting with `sso-` by the projectId
|
|
272
|
+
* inside it. The global token has no projectId, so it must never be picked
|
|
273
|
+
* up by that parser.
|
|
274
|
+
*/
|
|
275
|
+
expect(CookieUtil.getGlobalSSOKey()).toBe(CookieName.GlobalSSOToken);
|
|
276
|
+
expect(
|
|
277
|
+
CookieUtil.getGlobalSSOKey().startsWith(CookieUtil.getSSOKey()),
|
|
278
|
+
).toBe(false);
|
|
279
|
+
|
|
280
|
+
const token: string = CookieUtil.getGlobalSSOToken({
|
|
281
|
+
user: buildUser(ObjectID.generate()),
|
|
282
|
+
ssoProviderId: ObjectID.generate(),
|
|
283
|
+
ssoProviderType: SsoProviderType.GlobalSSO,
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
expect(UserMiddleware.getSsoTokens(buildCookieRequest(token))).toEqual({});
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
describe("UserMiddleware.doesSsoTokenForProjectExist - global token expiry", () => {
|
|
291
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
292
|
+
const userId: ObjectID = ObjectID.generate();
|
|
293
|
+
const ssoProviderId: ObjectID = ObjectID.generate();
|
|
294
|
+
|
|
295
|
+
const mintExpiredGlobalToken: () => string = (): string => {
|
|
296
|
+
return JSONWebToken.sign({
|
|
297
|
+
data: {
|
|
298
|
+
userId: userId,
|
|
299
|
+
name: new Name("Global SSO User"),
|
|
300
|
+
email: new Email("global-sso@oneuptime.com"),
|
|
301
|
+
isMasterAdmin: false,
|
|
302
|
+
isGeneralLogin: false,
|
|
303
|
+
ssoProviderId: ssoProviderId.toString(),
|
|
304
|
+
ssoProviderType: SsoProviderType.GlobalSSO.toString(),
|
|
305
|
+
},
|
|
306
|
+
expiresInSeconds: -60,
|
|
307
|
+
});
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
test("the minted token really is expired (guards the fixture itself)", () => {
|
|
311
|
+
const payload: JSONObject = jwt.decode(
|
|
312
|
+
mintExpiredGlobalToken(),
|
|
313
|
+
) as unknown as JSONObject;
|
|
314
|
+
|
|
315
|
+
expect((payload["exp"] as number) * 1000).toBeLessThan(Date.now());
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
test("expired global token in the cookie -> false", () => {
|
|
319
|
+
const req: ExpressRequest = buildCookieRequest(mintExpiredGlobalToken());
|
|
320
|
+
|
|
321
|
+
expect(UserMiddleware.getGlobalSsoTokenData(req)).toBeNull();
|
|
322
|
+
expect(
|
|
323
|
+
UserMiddleware.doesSsoTokenForProjectExist(req, projectId, userId),
|
|
324
|
+
).toBe(false);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
test("expired global token in the x-global-sso-token header (mobile) -> false", () => {
|
|
328
|
+
const req: ExpressRequest = buildHeaderRequest(mintExpiredGlobalToken());
|
|
329
|
+
|
|
330
|
+
expect(
|
|
331
|
+
UserMiddleware.doesSsoTokenForProjectExist(req, projectId, userId),
|
|
332
|
+
).toBe(false);
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
describe("UserMiddleware.doesSsoTokenForProjectExist - global token tampering", () => {
|
|
337
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
338
|
+
const userId: ObjectID = ObjectID.generate();
|
|
339
|
+
const ssoProviderId: ObjectID = ObjectID.generate();
|
|
340
|
+
|
|
341
|
+
const mintValidGlobalToken: () => string = (): string => {
|
|
342
|
+
return CookieUtil.getGlobalSSOToken({
|
|
343
|
+
user: buildUser(userId),
|
|
344
|
+
ssoProviderId,
|
|
345
|
+
ssoProviderType: SsoProviderType.GlobalSSO,
|
|
346
|
+
});
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
test("payload edited to point at another user -> false", () => {
|
|
350
|
+
const victimUserId: ObjectID = ObjectID.generate();
|
|
351
|
+
|
|
352
|
+
const forged: string = tamperWithPayload(
|
|
353
|
+
mintValidGlobalToken(),
|
|
354
|
+
(payload: JSONObject): void => {
|
|
355
|
+
payload["userId"] = victimUserId.toString();
|
|
356
|
+
},
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
const req: ExpressRequest = buildCookieRequest(forged);
|
|
360
|
+
|
|
361
|
+
expect(UserMiddleware.getGlobalSsoTokenData(req)).toBeNull();
|
|
362
|
+
expect(
|
|
363
|
+
UserMiddleware.doesSsoTokenForProjectExist(req, projectId, victimUserId),
|
|
364
|
+
).toBe(false);
|
|
365
|
+
// The original owner does not get in with it either.
|
|
366
|
+
expect(
|
|
367
|
+
UserMiddleware.doesSsoTokenForProjectExist(req, projectId, userId),
|
|
368
|
+
).toBe(false);
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
test("payload edited to claim a different SSO provider -> false", () => {
|
|
372
|
+
const requiredProviderId: ObjectID = ObjectID.generate();
|
|
373
|
+
|
|
374
|
+
const forged: string = tamperWithPayload(
|
|
375
|
+
mintValidGlobalToken(),
|
|
376
|
+
(payload: JSONObject): void => {
|
|
377
|
+
payload["ssoProviderId"] = requiredProviderId.toString();
|
|
378
|
+
},
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
expect(
|
|
382
|
+
UserMiddleware.doesSsoTokenForProjectExist(
|
|
383
|
+
buildCookieRequest(forged),
|
|
384
|
+
projectId,
|
|
385
|
+
userId,
|
|
386
|
+
requiredProviderId,
|
|
387
|
+
),
|
|
388
|
+
).toBe(false);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
test("payload edited to stretch the expiry -> false", () => {
|
|
392
|
+
const forged: string = tamperWithPayload(
|
|
393
|
+
mintValidGlobalToken(),
|
|
394
|
+
(payload: JSONObject): void => {
|
|
395
|
+
payload["exp"] =
|
|
396
|
+
Math.floor(Date.now() / 1000) + 10 * THIRTY_DAYS_IN_SECONDS;
|
|
397
|
+
},
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
expect(
|
|
401
|
+
UserMiddleware.doesSsoTokenForProjectExist(
|
|
402
|
+
buildCookieRequest(forged),
|
|
403
|
+
projectId,
|
|
404
|
+
userId,
|
|
405
|
+
),
|
|
406
|
+
).toBe(false);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
test("token signed with a different secret -> false", () => {
|
|
410
|
+
const foreignToken: string = jwt.sign(
|
|
411
|
+
{
|
|
412
|
+
userId: userId.toString(),
|
|
413
|
+
name: "Global SSO User",
|
|
414
|
+
email: "global-sso@oneuptime.com",
|
|
415
|
+
isMasterAdmin: false,
|
|
416
|
+
isGeneralLogin: false,
|
|
417
|
+
ssoProviderId: ssoProviderId.toString(),
|
|
418
|
+
ssoProviderType: SsoProviderType.GlobalSSO,
|
|
419
|
+
},
|
|
420
|
+
`${EncryptionSecret.toString()}-not-the-real-secret`,
|
|
421
|
+
{ expiresIn: THIRTY_DAYS_IN_SECONDS },
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
// Sanity: the same payload signed with the real secret WOULD be accepted.
|
|
425
|
+
expect(
|
|
426
|
+
UserMiddleware.doesSsoTokenForProjectExist(
|
|
427
|
+
buildCookieRequest(mintValidGlobalToken()),
|
|
428
|
+
projectId,
|
|
429
|
+
userId,
|
|
430
|
+
),
|
|
431
|
+
).toBe(true);
|
|
432
|
+
|
|
433
|
+
expect(
|
|
434
|
+
UserMiddleware.doesSsoTokenForProjectExist(
|
|
435
|
+
buildCookieRequest(foreignToken),
|
|
436
|
+
projectId,
|
|
437
|
+
userId,
|
|
438
|
+
),
|
|
439
|
+
).toBe(false);
|
|
440
|
+
expect(
|
|
441
|
+
UserMiddleware.doesSsoTokenForProjectExist(
|
|
442
|
+
buildHeaderRequest(foreignToken),
|
|
443
|
+
projectId,
|
|
444
|
+
userId,
|
|
445
|
+
),
|
|
446
|
+
).toBe(false);
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
test("syntactically broken tokens are rejected and do not throw out of the middleware", () => {
|
|
450
|
+
const brokenTokens: Array<string> = [
|
|
451
|
+
"not-a-jwt",
|
|
452
|
+
"",
|
|
453
|
+
"a.b.c",
|
|
454
|
+
"eyJhbGciOiJIUzI1NiJ9..",
|
|
455
|
+
`${mintValidGlobalToken()}-extra`,
|
|
456
|
+
];
|
|
457
|
+
|
|
458
|
+
for (const brokenToken of brokenTokens) {
|
|
459
|
+
for (const req of [
|
|
460
|
+
buildCookieRequest(brokenToken),
|
|
461
|
+
buildHeaderRequest(brokenToken),
|
|
462
|
+
]) {
|
|
463
|
+
const evaluate: () => boolean = (): boolean => {
|
|
464
|
+
return UserMiddleware.doesSsoTokenForProjectExist(
|
|
465
|
+
req,
|
|
466
|
+
projectId,
|
|
467
|
+
userId,
|
|
468
|
+
);
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
expect(evaluate).not.toThrow();
|
|
472
|
+
expect(evaluate()).toBe(false);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
describe("UserMiddleware.doesSsoTokenForProjectExist - global slot type confusion", () => {
|
|
479
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
480
|
+
const otherProjectId: ObjectID = ObjectID.generate();
|
|
481
|
+
const userId: ObjectID = ObjectID.generate();
|
|
482
|
+
const ssoProviderId: ObjectID = ObjectID.generate();
|
|
483
|
+
|
|
484
|
+
/*
|
|
485
|
+
* UserAuthorizationSSOProvider.test.ts already covers a ProjectSSO-typed
|
|
486
|
+
* token sitting in the global COOKIE slot. These cover the remaining
|
|
487
|
+
* directions: the ProjectOIDC type, the mobile header slot, and a token with
|
|
488
|
+
* no provider type at all.
|
|
489
|
+
*/
|
|
490
|
+
|
|
491
|
+
test("ProjectOIDC-typed token in the global cookie slot is ignored -> false", () => {
|
|
492
|
+
const token: string = CookieUtil.getSSOToken({
|
|
493
|
+
user: buildUser(userId),
|
|
494
|
+
projectId,
|
|
495
|
+
ssoProviderId,
|
|
496
|
+
ssoProviderType: SsoProviderType.ProjectOIDC,
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
const req: ExpressRequest = buildCookieRequest(token);
|
|
500
|
+
|
|
501
|
+
expect(UserMiddleware.getGlobalSsoTokenData(req)).toBeNull();
|
|
502
|
+
// otherProjectId has no per-project cookie, so only the global path could help.
|
|
503
|
+
expect(
|
|
504
|
+
UserMiddleware.doesSsoTokenForProjectExist(req, otherProjectId, userId),
|
|
505
|
+
).toBe(false);
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
test("project-typed token in the x-global-sso-token header is ignored -> false", () => {
|
|
509
|
+
const token: string = CookieUtil.getSSOToken({
|
|
510
|
+
user: buildUser(userId),
|
|
511
|
+
projectId,
|
|
512
|
+
ssoProviderId,
|
|
513
|
+
ssoProviderType: SsoProviderType.ProjectSSO,
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
const req: ExpressRequest = buildHeaderRequest(token);
|
|
517
|
+
|
|
518
|
+
expect(UserMiddleware.getGlobalSsoTokenData(req)).toBeNull();
|
|
519
|
+
expect(
|
|
520
|
+
UserMiddleware.doesSsoTokenForProjectExist(req, otherProjectId, userId),
|
|
521
|
+
).toBe(false);
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
test("token with NO ssoProviderType in the global slot is ignored -> false", () => {
|
|
525
|
+
const token: string = JSONWebToken.sign({
|
|
526
|
+
data: {
|
|
527
|
+
userId: userId,
|
|
528
|
+
name: new Name("Global SSO User"),
|
|
529
|
+
email: new Email("global-sso@oneuptime.com"),
|
|
530
|
+
isMasterAdmin: false,
|
|
531
|
+
isGeneralLogin: false,
|
|
532
|
+
ssoProviderId: ssoProviderId.toString(),
|
|
533
|
+
},
|
|
534
|
+
expiresInSeconds: THIRTY_DAYS_IN_SECONDS,
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
// The token itself verifies fine - it is the missing type that disqualifies it.
|
|
538
|
+
expect(JSONWebToken.decode(token).ssoProviderType).toBeUndefined();
|
|
539
|
+
|
|
540
|
+
for (const req of [buildCookieRequest(token), buildHeaderRequest(token)]) {
|
|
541
|
+
expect(UserMiddleware.getGlobalSsoTokenData(req)).toBeNull();
|
|
542
|
+
expect(
|
|
543
|
+
UserMiddleware.doesSsoTokenForProjectExist(req, otherProjectId, userId),
|
|
544
|
+
).toBe(false);
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
/*
|
|
550
|
+
* Identity: UserAuthorizationSSOProvider.test.ts already covers the wrong-user
|
|
551
|
+
* global token arriving via the COOKIE. This covers the mobile header path,
|
|
552
|
+
* which is the transport Global SSO now uses from the app.
|
|
553
|
+
*/
|
|
554
|
+
describe("UserMiddleware.doesSsoTokenForProjectExist - global token identity", () => {
|
|
555
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
556
|
+
const tokenOwnerId: ObjectID = ObjectID.generate();
|
|
557
|
+
|
|
558
|
+
test("valid global token for a DIFFERENT user, sent via header -> false", () => {
|
|
559
|
+
const token: string = CookieUtil.getGlobalSSOToken({
|
|
560
|
+
user: buildUser(tokenOwnerId),
|
|
561
|
+
ssoProviderId: ObjectID.generate(),
|
|
562
|
+
ssoProviderType: SsoProviderType.GlobalOIDC,
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
const req: ExpressRequest = buildHeaderRequest(token);
|
|
566
|
+
|
|
567
|
+
// The token is valid and readable...
|
|
568
|
+
const globalTokenData: JSONWebTokenData | null =
|
|
569
|
+
UserMiddleware.getGlobalSsoTokenData(req);
|
|
570
|
+
expect(globalTokenData?.userId.toString()).toBe(tokenOwnerId.toString());
|
|
571
|
+
|
|
572
|
+
// ...but it only satisfies the user it was minted for.
|
|
573
|
+
const otherUserId: ObjectID = ObjectID.generate();
|
|
574
|
+
expect(
|
|
575
|
+
UserMiddleware.doesSsoTokenForProjectExist(req, projectId, otherUserId),
|
|
576
|
+
).toBe(false);
|
|
577
|
+
expect(
|
|
578
|
+
UserMiddleware.doesSsoTokenForProjectExist(req, projectId, tokenOwnerId),
|
|
579
|
+
).toBe(true);
|
|
580
|
+
});
|
|
581
|
+
});
|