@enterprisestandard/esv 0.0.5-beta.20260115.2 → 0.0.5-beta.20260115.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/dist/iam/index.d.ts +8 -5
- package/dist/iam/index.js +5755 -664
- package/dist/iam/index.js.map +1 -1
- package/dist/index.d.ts +90 -10
- package/dist/index.js +6897 -152
- package/dist/index.js.map +1 -1
- package/dist/runner.d.ts +0 -36
- package/dist/runner.js +11407 -283
- package/dist/runner.js.map +1 -1
- package/dist/server/index.d.ts +88 -14
- package/dist/server/index.js +1387 -33
- package/dist/server/index.js.map +1 -1
- package/dist/sso/index.d.ts +8 -5
- package/dist/sso/index.js +365 -357
- package/dist/sso/index.js.map +1 -1
- package/dist/{types.d.ts → types-Bn1pr_xY.d.ts} +13 -11
- package/dist/workload/index.d.ts +8 -5
- package/dist/workload/index.js +393 -403
- package/dist/workload/index.js.map +1 -1
- package/package.json +2 -4
- package/dist/iam/index.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/runner.d.ts.map +0 -1
- package/dist/server/crypto.d.ts +0 -46
- package/dist/server/crypto.d.ts.map +0 -1
- package/dist/server/crypto.js +0 -134
- package/dist/server/crypto.js.map +0 -1
- package/dist/server/iam.d.ts +0 -11
- package/dist/server/iam.d.ts.map +0 -1
- package/dist/server/iam.js +0 -402
- package/dist/server/iam.js.map +0 -1
- package/dist/server/index.d.ts.map +0 -1
- package/dist/server/server.d.ts +0 -66
- package/dist/server/server.d.ts.map +0 -1
- package/dist/server/server.js +0 -223
- package/dist/server/server.js.map +0 -1
- package/dist/server/sso.d.ts +0 -11
- package/dist/server/sso.d.ts.map +0 -1
- package/dist/server/sso.js +0 -428
- package/dist/server/sso.js.map +0 -1
- package/dist/server/state.d.ts +0 -137
- package/dist/server/state.d.ts.map +0 -1
- package/dist/server/state.js +0 -152
- package/dist/server/state.js.map +0 -1
- package/dist/server/vault.d.ts +0 -11
- package/dist/server/vault.d.ts.map +0 -1
- package/dist/server/vault.js +0 -92
- package/dist/server/vault.js.map +0 -1
- package/dist/server/workload.d.ts +0 -19
- package/dist/server/workload.d.ts.map +0 -1
- package/dist/server/workload.js +0 -226
- package/dist/server/workload.js.map +0 -1
- package/dist/sso/index.d.ts.map +0 -1
- package/dist/tenant/index.d.ts +0 -17
- package/dist/tenant/index.d.ts.map +0 -1
- package/dist/tenant/index.js +0 -300
- package/dist/tenant/index.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
- package/dist/utils.d.ts +0 -75
- package/dist/utils.d.ts.map +0 -1
- package/dist/utils.js +0 -139
- package/dist/utils.js.map +0 -1
- package/dist/workload/index.d.ts.map +0 -1
package/dist/sso/index.js
CHANGED
|
@@ -1,376 +1,384 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const response = await fetch(config.loginPath);
|
|
26
|
-
// Should return a redirect (302)
|
|
27
|
-
assert(response.status === 302, `Expected 302 redirect, got ${response.status}`);
|
|
28
|
-
// Should have a Location header pointing to the authorization URL
|
|
29
|
-
const location = response.headers.get('Location');
|
|
30
|
-
assert(location !== null, 'Missing Location header in redirect');
|
|
31
|
-
// If an expected pattern is provided, validate the URL
|
|
32
|
-
if (config.expectedAuthorizationUrlPattern) {
|
|
33
|
-
assert(config.expectedAuthorizationUrlPattern.test(location), `Authorization URL does not match expected pattern: ${location}`);
|
|
34
|
-
}
|
|
35
|
-
// Should have required OIDC parameters
|
|
36
|
-
const url = new URL(location);
|
|
37
|
-
assert(url.searchParams.has('client_id'), 'Missing client_id in authorization URL');
|
|
38
|
-
assert(url.searchParams.has('redirect_uri'), 'Missing redirect_uri in authorization URL');
|
|
39
|
-
assert(url.searchParams.has('response_type'), 'Missing response_type in authorization URL');
|
|
40
|
-
assert(url.searchParams.has('scope'), 'Missing scope in authorization URL');
|
|
41
|
-
assert(url.searchParams.has('state'), 'Missing state in authorization URL');
|
|
42
|
-
assert(url.searchParams.has('code_challenge'), 'Missing code_challenge (PKCE) in authorization URL');
|
|
43
|
-
assert(url.searchParams.has('code_challenge_method'), 'Missing code_challenge_method (PKCE) in authorization URL');
|
|
44
|
-
// Should set a state cookie
|
|
45
|
-
const cookies = parseCookies(response.headers);
|
|
46
|
-
const hasStateCookie = Array.from(cookies.keys()).some((name) => name.includes('.state'));
|
|
47
|
-
assert(hasStateCookie, 'Missing state cookie in response');
|
|
48
|
-
return {
|
|
49
|
-
details: {
|
|
50
|
-
authorizationUrl: location,
|
|
51
|
-
pkceEnabled: true,
|
|
52
|
-
stateParameter: url.searchParams.get('state'),
|
|
53
|
-
},
|
|
54
|
-
};
|
|
55
|
-
});
|
|
1
|
+
// src/utils.ts
|
|
2
|
+
function createFetcher(config) {
|
|
3
|
+
const timeout = config.timeout ?? 5e3;
|
|
4
|
+
const headers = config.headers ?? {};
|
|
5
|
+
return async function fetcher(path, options = {}) {
|
|
6
|
+
const url = `${config.baseUrl}${path}`;
|
|
7
|
+
const controller = new AbortController();
|
|
8
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
9
|
+
try {
|
|
10
|
+
const response = await fetch(url, {
|
|
11
|
+
...options,
|
|
12
|
+
signal: controller.signal,
|
|
13
|
+
headers: {
|
|
14
|
+
...headers,
|
|
15
|
+
...options.headers
|
|
16
|
+
},
|
|
17
|
+
redirect: "manual"
|
|
18
|
+
// Don't follow redirects automatically
|
|
19
|
+
});
|
|
20
|
+
return response;
|
|
21
|
+
} finally {
|
|
22
|
+
clearTimeout(timeoutId);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
56
25
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
26
|
+
async function runTest(name, testFn) {
|
|
27
|
+
const start = performance.now();
|
|
28
|
+
try {
|
|
29
|
+
const result = await testFn();
|
|
30
|
+
return {
|
|
31
|
+
name,
|
|
32
|
+
passed: true,
|
|
33
|
+
duration: performance.now() - start,
|
|
34
|
+
details: result?.details
|
|
35
|
+
};
|
|
36
|
+
} catch (error) {
|
|
37
|
+
return {
|
|
38
|
+
name,
|
|
39
|
+
passed: false,
|
|
40
|
+
error: error instanceof Error ? error.message : String(error),
|
|
41
|
+
duration: performance.now() - start
|
|
42
|
+
};
|
|
43
|
+
}
|
|
71
44
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
assert(response.status === 302 || response.status === 200, `Expected 302 or 200, got ${response.status}`);
|
|
80
|
-
// Should clear cookies
|
|
81
|
-
const cookies = parseCookies(response.headers);
|
|
82
|
-
const clearedCookies = [];
|
|
83
|
-
for (const [name, value] of cookies.entries()) {
|
|
84
|
-
// Check if the cookie is being cleared (Max-Age=0 or empty value)
|
|
85
|
-
if (value === '' || value.includes('Max-Age=0')) {
|
|
86
|
-
clearedCookies.push(name);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
// Check for standard SSO cookies being cleared
|
|
90
|
-
const setCookieHeaders = response.headers.getSetCookie?.() ?? [];
|
|
91
|
-
const hasExpiredCookies = setCookieHeaders.some((cookie) => cookie.includes('Max-Age=0') || cookie.includes('expires=Thu, 01 Jan 1970'));
|
|
92
|
-
assert(hasExpiredCookies || clearedCookies.length > 0 || setCookieHeaders.length === 0, 'Logout should clear session cookies');
|
|
93
|
-
return {
|
|
94
|
-
details: {
|
|
95
|
-
status: response.status,
|
|
96
|
-
clearedCookies,
|
|
97
|
-
},
|
|
98
|
-
};
|
|
99
|
-
});
|
|
45
|
+
function skipTest(name, reason) {
|
|
46
|
+
return {
|
|
47
|
+
name,
|
|
48
|
+
passed: true,
|
|
49
|
+
duration: 0,
|
|
50
|
+
details: { skipped: true, reason }
|
|
51
|
+
};
|
|
100
52
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
return runTest('SSO Back-Channel Logout Endpoint', async () => {
|
|
106
|
-
// Send a POST request without a valid logout token
|
|
107
|
-
// The endpoint should exist and return 400 (bad request) for missing token
|
|
108
|
-
const response = await fetch(config.backChannelLogoutPath, {
|
|
109
|
-
method: 'POST',
|
|
110
|
-
headers: {
|
|
111
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
112
|
-
},
|
|
113
|
-
body: '',
|
|
114
|
-
});
|
|
115
|
-
// Should return 400 (missing logout_token) or 200 (if session store not configured)
|
|
116
|
-
assert(response.status === 400 || response.status === 200 || response.status === 404, `Expected 400 or 200, got ${response.status}`);
|
|
117
|
-
return {
|
|
118
|
-
details: {
|
|
119
|
-
status: response.status,
|
|
120
|
-
endpointExists: response.status !== 404,
|
|
121
|
-
},
|
|
122
|
-
};
|
|
123
|
-
});
|
|
53
|
+
function assert(condition, message) {
|
|
54
|
+
if (!condition) {
|
|
55
|
+
throw new Error(message);
|
|
56
|
+
}
|
|
124
57
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
status: response.status,
|
|
137
|
-
handlesInvalidCallback: true,
|
|
138
|
-
},
|
|
139
|
-
};
|
|
140
|
-
});
|
|
58
|
+
function parseCookies(headers) {
|
|
59
|
+
const cookies = /* @__PURE__ */ new Map();
|
|
60
|
+
const setCookieHeaders = headers.getSetCookie?.() ?? [];
|
|
61
|
+
for (const cookie of setCookieHeaders) {
|
|
62
|
+
const [pair] = cookie.split(";");
|
|
63
|
+
const [name, value] = pair.split("=");
|
|
64
|
+
if (name && value !== void 0) {
|
|
65
|
+
cookies.set(name.trim(), value.trim());
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return cookies;
|
|
141
69
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
70
|
+
function buildCookieHeader(cookies) {
|
|
71
|
+
return Array.from(cookies.entries()).map(([name, value]) => `${name}=${value}`).join("; ");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/sso/index.ts
|
|
75
|
+
var DEFAULT_CONFIG = {
|
|
76
|
+
loginPath: "/api/auth/login",
|
|
77
|
+
callbackPath: "/api/auth/callback",
|
|
78
|
+
userPath: "/api/auth/user",
|
|
79
|
+
logoutPath: "/api/auth/logout",
|
|
80
|
+
backChannelLogoutPath: "/api/auth/logout/backchannel",
|
|
81
|
+
tokenPath: "/api/auth/token",
|
|
82
|
+
refreshPath: "/api/auth/refresh"
|
|
83
|
+
};
|
|
84
|
+
async function testLoginEndpoint(config, fetch2) {
|
|
85
|
+
return runTest("SSO Login Endpoint", async () => {
|
|
86
|
+
const response = await fetch2(config.loginPath);
|
|
87
|
+
assert(response.status === 302, `Expected 302 redirect, got ${response.status}`);
|
|
88
|
+
const location = response.headers.get("Location");
|
|
89
|
+
assert(location !== null, "Missing Location header in redirect");
|
|
90
|
+
if (config.expectedAuthorizationUrlPattern) {
|
|
91
|
+
assert(
|
|
92
|
+
config.expectedAuthorizationUrlPattern.test(location),
|
|
93
|
+
`Authorization URL does not match expected pattern: ${location}`
|
|
94
|
+
);
|
|
148
95
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
96
|
+
const url = new URL(location);
|
|
97
|
+
assert(url.searchParams.has("client_id"), "Missing client_id in authorization URL");
|
|
98
|
+
assert(url.searchParams.has("redirect_uri"), "Missing redirect_uri in authorization URL");
|
|
99
|
+
assert(url.searchParams.has("response_type"), "Missing response_type in authorization URL");
|
|
100
|
+
assert(url.searchParams.has("scope"), "Missing scope in authorization URL");
|
|
101
|
+
assert(url.searchParams.has("state"), "Missing state in authorization URL");
|
|
102
|
+
assert(url.searchParams.has("code_challenge"), "Missing code_challenge (PKCE) in authorization URL");
|
|
103
|
+
assert(url.searchParams.has("code_challenge_method"), "Missing code_challenge_method (PKCE) in authorization URL");
|
|
104
|
+
const cookies = parseCookies(response.headers);
|
|
105
|
+
const hasStateCookie = Array.from(cookies.keys()).some((name) => name.includes(".state"));
|
|
106
|
+
assert(hasStateCookie, "Missing state cookie in response");
|
|
107
|
+
return {
|
|
108
|
+
details: {
|
|
109
|
+
authorizationUrl: location,
|
|
110
|
+
pkceEnabled: true,
|
|
111
|
+
stateParameter: url.searchParams.get("state")
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
async function testUserEndpointUnauthenticated(config, fetch2) {
|
|
117
|
+
return runTest("SSO User Endpoint (Unauthenticated)", async () => {
|
|
118
|
+
const response = await fetch2(config.userPath);
|
|
119
|
+
assert(response.status === 401, `Expected 401 Unauthorized, got ${response.status}`);
|
|
120
|
+
return {
|
|
121
|
+
details: {
|
|
122
|
+
status: response.status
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
});
|
|
159
126
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
127
|
+
async function testLogoutEndpoint(config, fetch2) {
|
|
128
|
+
return runTest("SSO Logout Endpoint", async () => {
|
|
129
|
+
const response = await fetch2(`${config.logoutPath}?redirect=/`);
|
|
130
|
+
assert(response.status === 302 || response.status === 200, `Expected 302 or 200, got ${response.status}`);
|
|
131
|
+
const cookies = parseCookies(response.headers);
|
|
132
|
+
const clearedCookies = [];
|
|
133
|
+
for (const [name, value] of cookies.entries()) {
|
|
134
|
+
if (value === "" || value.includes("Max-Age=0")) {
|
|
135
|
+
clearedCookies.push(name);
|
|
136
|
+
}
|
|
166
137
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
138
|
+
const setCookieHeaders = response.headers.getSetCookie?.() ?? [];
|
|
139
|
+
const hasExpiredCookies = setCookieHeaders.some(
|
|
140
|
+
(cookie) => cookie.includes("Max-Age=0") || cookie.includes("expires=Thu, 01 Jan 1970")
|
|
141
|
+
);
|
|
142
|
+
assert(
|
|
143
|
+
hasExpiredCookies || clearedCookies.length > 0 || setCookieHeaders.length === 0,
|
|
144
|
+
"Logout should clear session cookies"
|
|
145
|
+
);
|
|
146
|
+
return {
|
|
147
|
+
details: {
|
|
148
|
+
status: response.status,
|
|
149
|
+
clearedCookies
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
async function testBackChannelLogoutEndpoint(config, fetch2) {
|
|
155
|
+
return runTest("SSO Back-Channel Logout Endpoint", async () => {
|
|
156
|
+
const response = await fetch2(config.backChannelLogoutPath, {
|
|
157
|
+
method: "POST",
|
|
158
|
+
headers: {
|
|
159
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
160
|
+
},
|
|
161
|
+
body: ""
|
|
176
162
|
});
|
|
163
|
+
assert(
|
|
164
|
+
response.status === 400 || response.status === 200 || response.status === 404,
|
|
165
|
+
`Expected 400 or 200, got ${response.status}`
|
|
166
|
+
);
|
|
167
|
+
return {
|
|
168
|
+
details: {
|
|
169
|
+
status: response.status,
|
|
170
|
+
endpointExists: response.status !== 404
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
});
|
|
177
174
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
175
|
+
async function testCallbackEndpointInvalid(config, fetch2) {
|
|
176
|
+
return runTest("SSO Callback Endpoint (Invalid)", async () => {
|
|
177
|
+
const response = await fetch2(`${config.callbackPath}?code=invalid&state=invalid`);
|
|
178
|
+
assert(response.status >= 400, `Expected error status for invalid callback, got ${response.status}`);
|
|
179
|
+
return {
|
|
180
|
+
details: {
|
|
181
|
+
status: response.status,
|
|
182
|
+
handlesInvalidCallback: true
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
async function testTokenEndpointUnauthenticated(config, fetch2) {
|
|
188
|
+
if (!config.tokenPath) {
|
|
189
|
+
return skipTest("SSO Token Endpoint (Unauthenticated)", "Token path not configured");
|
|
190
|
+
}
|
|
191
|
+
return runTest("SSO Token Endpoint (Unauthenticated)", async () => {
|
|
192
|
+
const response = await fetch2(config.tokenPath);
|
|
193
|
+
assert(
|
|
194
|
+
response.status === 401 || response.status === 404,
|
|
195
|
+
`Expected 401 Unauthorized or 404, got ${response.status}`
|
|
196
|
+
);
|
|
197
|
+
return {
|
|
198
|
+
details: {
|
|
199
|
+
status: response.status
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
async function testRefreshEndpointUnauthenticated(config, fetch2) {
|
|
205
|
+
if (!config.refreshPath) {
|
|
206
|
+
return skipTest("SSO Refresh Endpoint (Unauthenticated)", "Refresh path not configured");
|
|
207
|
+
}
|
|
208
|
+
return runTest("SSO Refresh Endpoint (Unauthenticated)", async () => {
|
|
209
|
+
const response = await fetch2(config.refreshPath);
|
|
210
|
+
assert(
|
|
211
|
+
response.status === 401 || response.status === 404,
|
|
212
|
+
`Expected 401 Unauthorized or 404, got ${response.status}`
|
|
213
|
+
);
|
|
214
|
+
return {
|
|
215
|
+
details: {
|
|
216
|
+
status: response.status
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
async function completeLoginFlow(config, fetch2) {
|
|
222
|
+
try {
|
|
223
|
+
const loginResponse = await fetch2(config.loginPath);
|
|
224
|
+
if (loginResponse.status !== 302) {
|
|
225
|
+
return { cookies: /* @__PURE__ */ new Map(), success: false, error: `Login did not redirect: ${loginResponse.status}` };
|
|
220
226
|
}
|
|
221
|
-
|
|
222
|
-
|
|
227
|
+
const authUrl = loginResponse.headers.get("Location");
|
|
228
|
+
if (!authUrl) {
|
|
229
|
+
return { cookies: /* @__PURE__ */ new Map(), success: false, error: "No redirect location from login" };
|
|
223
230
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
assert(hasAuthCookies, 'No authentication cookies set after login');
|
|
239
|
-
// Verify we can now access the user endpoint
|
|
240
|
-
const userResponse = await fetch(config.userPath, {
|
|
241
|
-
headers: {
|
|
242
|
-
Cookie: buildCookieHeader(cookies),
|
|
243
|
-
},
|
|
244
|
-
});
|
|
245
|
-
assert(userResponse.status === 200, `User endpoint returned ${userResponse.status} after login, expected 200`);
|
|
246
|
-
const userData = await userResponse.json();
|
|
247
|
-
assert(userData.id, 'User data missing id field');
|
|
248
|
-
return {
|
|
249
|
-
details: {
|
|
250
|
-
userId: userData.id,
|
|
251
|
-
userName: userData.userName,
|
|
252
|
-
email: userData.email,
|
|
253
|
-
jitFlowCompleted: true,
|
|
254
|
-
},
|
|
255
|
-
};
|
|
231
|
+
const loginCookies = parseCookies(loginResponse.headers);
|
|
232
|
+
const authResponse = await globalThis.fetch(authUrl, { redirect: "manual" });
|
|
233
|
+
if (authResponse.status !== 302) {
|
|
234
|
+
return { cookies: /* @__PURE__ */ new Map(), success: false, error: `Auth did not redirect: ${authResponse.status}` };
|
|
235
|
+
}
|
|
236
|
+
const callbackUrl = authResponse.headers.get("Location");
|
|
237
|
+
if (!callbackUrl) {
|
|
238
|
+
return { cookies: /* @__PURE__ */ new Map(), success: false, error: "No redirect location from auth" };
|
|
239
|
+
}
|
|
240
|
+
const callbackPath = new URL(callbackUrl).pathname + new URL(callbackUrl).search;
|
|
241
|
+
const callbackResponse = await fetch2(callbackPath, {
|
|
242
|
+
headers: {
|
|
243
|
+
Cookie: buildCookieHeader(loginCookies)
|
|
244
|
+
}
|
|
256
245
|
});
|
|
246
|
+
if (callbackResponse.status !== 302) {
|
|
247
|
+
return { cookies: /* @__PURE__ */ new Map(), success: false, error: `Callback did not redirect: ${callbackResponse.status}` };
|
|
248
|
+
}
|
|
249
|
+
const allCookies = parseCookies(callbackResponse.headers);
|
|
250
|
+
return { cookies: allCookies, success: true };
|
|
251
|
+
} catch (error) {
|
|
252
|
+
return { cookies: /* @__PURE__ */ new Map(), success: false, error: error instanceof Error ? error.message : String(error) };
|
|
253
|
+
}
|
|
257
254
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
tests.push(await testTokenEndpointUnauthenticated(mergedConfig, fetch));
|
|
273
|
-
tests.push(await testRefreshEndpointUnauthenticated(mergedConfig, fetch));
|
|
274
|
-
tests.push(await testJitUserProvisioningFlow(mergedConfig, fetch));
|
|
275
|
-
const duration = performance.now() - startTime;
|
|
276
|
-
const passed = tests.filter((t) => t.passed).length;
|
|
277
|
-
const failed = tests.filter((t) => !t.passed).length;
|
|
278
|
-
const skipped = tests.filter((t) => t.details?.skipped).length;
|
|
255
|
+
async function testJitUserProvisioningFlow(config, fetch2) {
|
|
256
|
+
return runTest("SSO JIT User Provisioning Flow", async () => {
|
|
257
|
+
const { cookies, success, error } = await completeLoginFlow(config, fetch2);
|
|
258
|
+
assert(success, error || "Login flow failed");
|
|
259
|
+
const hasAuthCookies = Array.from(cookies.keys()).some((name) => name.includes(".access") || name.includes(".id"));
|
|
260
|
+
assert(hasAuthCookies, "No authentication cookies set after login");
|
|
261
|
+
const userResponse = await fetch2(config.userPath, {
|
|
262
|
+
headers: {
|
|
263
|
+
Cookie: buildCookieHeader(cookies)
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
assert(userResponse.status === 200, `User endpoint returned ${userResponse.status} after login, expected 200`);
|
|
267
|
+
const userData = await userResponse.json();
|
|
268
|
+
assert(userData.id, "User data missing id field");
|
|
279
269
|
return {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
passed: passed - skipped,
|
|
287
|
-
failed,
|
|
288
|
-
skipped,
|
|
289
|
-
},
|
|
270
|
+
details: {
|
|
271
|
+
userId: userData.id,
|
|
272
|
+
userName: userData.userName,
|
|
273
|
+
email: userData.email,
|
|
274
|
+
jitFlowCompleted: true
|
|
275
|
+
}
|
|
290
276
|
};
|
|
277
|
+
});
|
|
291
278
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
throw new Error(result.error);
|
|
322
|
-
},
|
|
323
|
-
},
|
|
324
|
-
{
|
|
325
|
-
name: 'token endpoint returns 401 when unauthenticated',
|
|
326
|
-
fn: async () => {
|
|
327
|
-
const result = await testTokenEndpointUnauthenticated(mergedConfig, fetch);
|
|
328
|
-
if (!result.passed)
|
|
329
|
-
throw new Error(result.error);
|
|
330
|
-
},
|
|
331
|
-
},
|
|
332
|
-
{
|
|
333
|
-
name: 'refresh endpoint returns 401 when unauthenticated',
|
|
334
|
-
fn: async () => {
|
|
335
|
-
const result = await testRefreshEndpointUnauthenticated(mergedConfig, fetch);
|
|
336
|
-
if (!result.passed)
|
|
337
|
-
throw new Error(result.error);
|
|
338
|
-
},
|
|
339
|
-
},
|
|
340
|
-
];
|
|
341
|
-
// Extension methods for optional functionality
|
|
342
|
-
const ext = {
|
|
343
|
-
createJITTests: () => [
|
|
344
|
-
{
|
|
345
|
-
name: 'user provisioning flow completes successfully',
|
|
346
|
-
fn: async () => {
|
|
347
|
-
const result = await testJitUserProvisioningFlow(mergedConfig, fetch);
|
|
348
|
-
if (!result.passed)
|
|
349
|
-
throw new Error(result.error);
|
|
350
|
-
},
|
|
351
|
-
},
|
|
352
|
-
],
|
|
353
|
-
createLogoutTests: () => [
|
|
354
|
-
{
|
|
355
|
-
name: 'logout endpoint clears cookies',
|
|
356
|
-
fn: async () => {
|
|
357
|
-
const result = await testLogoutEndpoint(mergedConfig, fetch);
|
|
358
|
-
if (!result.passed)
|
|
359
|
-
throw new Error(result.error);
|
|
360
|
-
},
|
|
361
|
-
},
|
|
362
|
-
],
|
|
363
|
-
createBackChannelLogoutTests: () => [
|
|
364
|
-
{
|
|
365
|
-
name: 'endpoint exists',
|
|
366
|
-
fn: async () => {
|
|
367
|
-
const result = await testBackChannelLogoutEndpoint(mergedConfig, fetch);
|
|
368
|
-
if (!result.passed)
|
|
369
|
-
throw new Error(result.error);
|
|
370
|
-
},
|
|
371
|
-
},
|
|
372
|
-
],
|
|
373
|
-
};
|
|
374
|
-
return { tests, ext };
|
|
279
|
+
async function validateSSO(config) {
|
|
280
|
+
const startTime = performance.now();
|
|
281
|
+
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
282
|
+
const fetch2 = createFetcher(mergedConfig);
|
|
283
|
+
const tests = [];
|
|
284
|
+
tests.push(await testLoginEndpoint(mergedConfig, fetch2));
|
|
285
|
+
tests.push(await testUserEndpointUnauthenticated(mergedConfig, fetch2));
|
|
286
|
+
tests.push(await testLogoutEndpoint(mergedConfig, fetch2));
|
|
287
|
+
tests.push(await testBackChannelLogoutEndpoint(mergedConfig, fetch2));
|
|
288
|
+
tests.push(await testCallbackEndpointInvalid(mergedConfig, fetch2));
|
|
289
|
+
tests.push(await testTokenEndpointUnauthenticated(mergedConfig, fetch2));
|
|
290
|
+
tests.push(await testRefreshEndpointUnauthenticated(mergedConfig, fetch2));
|
|
291
|
+
tests.push(await testJitUserProvisioningFlow(mergedConfig, fetch2));
|
|
292
|
+
const duration = performance.now() - startTime;
|
|
293
|
+
const passed = tests.filter((t) => t.passed).length;
|
|
294
|
+
const failed = tests.filter((t) => !t.passed).length;
|
|
295
|
+
const skipped = tests.filter((t) => t.details?.skipped).length;
|
|
296
|
+
return {
|
|
297
|
+
suite: "SSO",
|
|
298
|
+
passed: failed === 0,
|
|
299
|
+
tests,
|
|
300
|
+
duration,
|
|
301
|
+
summary: {
|
|
302
|
+
total: tests.length,
|
|
303
|
+
passed: passed - skipped,
|
|
304
|
+
failed,
|
|
305
|
+
skipped
|
|
306
|
+
}
|
|
307
|
+
};
|
|
375
308
|
}
|
|
309
|
+
function createSSOTests(config) {
|
|
310
|
+
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
311
|
+
const fetch2 = createFetcher(mergedConfig);
|
|
312
|
+
const tests = [
|
|
313
|
+
{
|
|
314
|
+
name: "login endpoint redirects to authorization URL",
|
|
315
|
+
fn: async () => {
|
|
316
|
+
const result = await testLoginEndpoint(mergedConfig, fetch2);
|
|
317
|
+
if (!result.passed) throw new Error(result.error);
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
name: "user endpoint returns 401 when unauthenticated",
|
|
322
|
+
fn: async () => {
|
|
323
|
+
const result = await testUserEndpointUnauthenticated(mergedConfig, fetch2);
|
|
324
|
+
if (!result.passed) throw new Error(result.error);
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
name: "callback endpoint rejects invalid requests",
|
|
329
|
+
fn: async () => {
|
|
330
|
+
const result = await testCallbackEndpointInvalid(mergedConfig, fetch2);
|
|
331
|
+
if (!result.passed) throw new Error(result.error);
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
name: "token endpoint returns 401 when unauthenticated",
|
|
336
|
+
fn: async () => {
|
|
337
|
+
const result = await testTokenEndpointUnauthenticated(mergedConfig, fetch2);
|
|
338
|
+
if (!result.passed) throw new Error(result.error);
|
|
339
|
+
}
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
name: "refresh endpoint returns 401 when unauthenticated",
|
|
343
|
+
fn: async () => {
|
|
344
|
+
const result = await testRefreshEndpointUnauthenticated(mergedConfig, fetch2);
|
|
345
|
+
if (!result.passed) throw new Error(result.error);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
];
|
|
349
|
+
const ext = {
|
|
350
|
+
createJITTests: () => [
|
|
351
|
+
{
|
|
352
|
+
name: "user provisioning flow completes successfully",
|
|
353
|
+
fn: async () => {
|
|
354
|
+
const result = await testJitUserProvisioningFlow(mergedConfig, fetch2);
|
|
355
|
+
if (!result.passed) throw new Error(result.error);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
],
|
|
359
|
+
createLogoutTests: () => [
|
|
360
|
+
{
|
|
361
|
+
name: "logout endpoint clears cookies",
|
|
362
|
+
fn: async () => {
|
|
363
|
+
const result = await testLogoutEndpoint(mergedConfig, fetch2);
|
|
364
|
+
if (!result.passed) throw new Error(result.error);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
],
|
|
368
|
+
createBackChannelLogoutTests: () => [
|
|
369
|
+
{
|
|
370
|
+
name: "endpoint exists",
|
|
371
|
+
fn: async () => {
|
|
372
|
+
const result = await testBackChannelLogoutEndpoint(mergedConfig, fetch2);
|
|
373
|
+
if (!result.passed) throw new Error(result.error);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
]
|
|
377
|
+
};
|
|
378
|
+
return { tests, ext };
|
|
379
|
+
}
|
|
380
|
+
export {
|
|
381
|
+
createSSOTests,
|
|
382
|
+
validateSSO
|
|
383
|
+
};
|
|
376
384
|
//# sourceMappingURL=index.js.map
|