@enterprisestandard/esv 0.0.5-beta.20260115.3 → 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.
Files changed (65) hide show
  1. package/dist/iam/index.d.ts +8 -5
  2. package/dist/iam/index.js +5755 -664
  3. package/dist/iam/index.js.map +1 -1
  4. package/dist/index.d.ts +90 -10
  5. package/dist/index.js +6897 -152
  6. package/dist/index.js.map +1 -1
  7. package/dist/runner.d.ts +0 -36
  8. package/dist/runner.js +11407 -283
  9. package/dist/runner.js.map +1 -1
  10. package/dist/server/index.d.ts +88 -14
  11. package/dist/server/index.js +1387 -33
  12. package/dist/server/index.js.map +1 -1
  13. package/dist/sso/index.d.ts +8 -5
  14. package/dist/sso/index.js +365 -357
  15. package/dist/sso/index.js.map +1 -1
  16. package/dist/{types.d.ts → types-Bn1pr_xY.d.ts} +13 -11
  17. package/dist/workload/index.d.ts +8 -5
  18. package/dist/workload/index.js +393 -403
  19. package/dist/workload/index.js.map +1 -1
  20. package/package.json +2 -4
  21. package/dist/iam/index.d.ts.map +0 -1
  22. package/dist/index.d.ts.map +0 -1
  23. package/dist/runner.d.ts.map +0 -1
  24. package/dist/server/crypto.d.ts +0 -46
  25. package/dist/server/crypto.d.ts.map +0 -1
  26. package/dist/server/crypto.js +0 -134
  27. package/dist/server/crypto.js.map +0 -1
  28. package/dist/server/iam.d.ts +0 -11
  29. package/dist/server/iam.d.ts.map +0 -1
  30. package/dist/server/iam.js +0 -402
  31. package/dist/server/iam.js.map +0 -1
  32. package/dist/server/index.d.ts.map +0 -1
  33. package/dist/server/server.d.ts +0 -66
  34. package/dist/server/server.d.ts.map +0 -1
  35. package/dist/server/server.js +0 -223
  36. package/dist/server/server.js.map +0 -1
  37. package/dist/server/sso.d.ts +0 -11
  38. package/dist/server/sso.d.ts.map +0 -1
  39. package/dist/server/sso.js +0 -428
  40. package/dist/server/sso.js.map +0 -1
  41. package/dist/server/state.d.ts +0 -137
  42. package/dist/server/state.d.ts.map +0 -1
  43. package/dist/server/state.js +0 -152
  44. package/dist/server/state.js.map +0 -1
  45. package/dist/server/vault.d.ts +0 -11
  46. package/dist/server/vault.d.ts.map +0 -1
  47. package/dist/server/vault.js +0 -92
  48. package/dist/server/vault.js.map +0 -1
  49. package/dist/server/workload.d.ts +0 -19
  50. package/dist/server/workload.d.ts.map +0 -1
  51. package/dist/server/workload.js +0 -226
  52. package/dist/server/workload.js.map +0 -1
  53. package/dist/sso/index.d.ts.map +0 -1
  54. package/dist/tenant/index.d.ts +0 -17
  55. package/dist/tenant/index.d.ts.map +0 -1
  56. package/dist/tenant/index.js +0 -300
  57. package/dist/tenant/index.js.map +0 -1
  58. package/dist/types.d.ts.map +0 -1
  59. package/dist/types.js +0 -2
  60. package/dist/types.js.map +0 -1
  61. package/dist/utils.d.ts +0 -75
  62. package/dist/utils.d.ts.map +0 -1
  63. package/dist/utils.js +0 -139
  64. package/dist/utils.js.map +0 -1
  65. package/dist/workload/index.d.ts.map +0 -1
package/dist/sso/index.js CHANGED
@@ -1,376 +1,384 @@
1
- /**
2
- * SSO Validation Tests
3
- *
4
- * These tests validate that an application correctly implements
5
- * Enterprise Standard SSO (Single Sign-On) functionality.
6
- */
7
- import { assert, buildCookieHeader, createFetcher, parseCookies, runTest, skipTest } from '../utils.js';
8
- /**
9
- * Default configuration for SSO validation
10
- */
11
- const DEFAULT_CONFIG = {
12
- loginPath: '/api/auth/login',
13
- callbackPath: '/api/auth/callback',
14
- userPath: '/api/auth/user',
15
- logoutPath: '/api/auth/logout',
16
- backChannelLogoutPath: '/api/auth/logout/backchannel',
17
- tokenPath: '/api/auth/token',
18
- refreshPath: '/api/auth/refresh',
19
- };
20
- /**
21
- * Test: Login endpoint exists and redirects to authorization URL
22
- */
23
- async function testLoginEndpoint(config, fetch) {
24
- return runTest('SSO Login Endpoint', async () => {
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
- * Test: User endpoint returns 401 when not authenticated
59
- */
60
- async function testUserEndpointUnauthenticated(config, fetch) {
61
- return runTest('SSO User Endpoint (Unauthenticated)', async () => {
62
- const response = await fetch(config.userPath);
63
- // Should return 401 Unauthorized
64
- assert(response.status === 401, `Expected 401 Unauthorized, got ${response.status}`);
65
- return {
66
- details: {
67
- status: response.status,
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
- * Test: Logout endpoint clears cookies
74
- */
75
- async function testLogoutEndpoint(config, fetch) {
76
- return runTest('SSO Logout Endpoint', async () => {
77
- const response = await fetch(`${config.logoutPath}?redirect=/`);
78
- // Should return 302 redirect (with redirect param) or 200
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
- * Test: Back-channel logout endpoint exists
103
- */
104
- async function testBackChannelLogoutEndpoint(config, fetch) {
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
- * Test: Callback endpoint returns error without valid code
127
- */
128
- async function testCallbackEndpointInvalid(config, fetch) {
129
- return runTest('SSO Callback Endpoint (Invalid)', async () => {
130
- // Send a callback request without proper state/code
131
- const response = await fetch(`${config.callbackPath}?code=invalid&state=invalid`);
132
- // Should return 400 or 500 (validation error)
133
- assert(response.status >= 400, `Expected error status for invalid callback, got ${response.status}`);
134
- return {
135
- details: {
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
- * Test: Token endpoint returns 401 when not authenticated
144
- */
145
- async function testTokenEndpointUnauthenticated(config, fetch) {
146
- if (!config.tokenPath) {
147
- return skipTest('SSO Token Endpoint (Unauthenticated)', 'Token path not configured');
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
- return runTest('SSO Token Endpoint (Unauthenticated)', async () => {
150
- const response = await fetch(config.tokenPath);
151
- // Should return 401 Unauthorized
152
- assert(response.status === 401 || response.status === 404, `Expected 401 Unauthorized or 404, got ${response.status}`);
153
- return {
154
- details: {
155
- status: response.status,
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
- * Test: Refresh endpoint returns 401 when not authenticated
162
- */
163
- async function testRefreshEndpointUnauthenticated(config, fetch) {
164
- if (!config.refreshPath) {
165
- return skipTest('SSO Refresh Endpoint (Unauthenticated)', 'Refresh path not configured');
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
- return runTest('SSO Refresh Endpoint (Unauthenticated)', async () => {
168
- const response = await fetch(config.refreshPath);
169
- // Should return 401 Unauthorized
170
- assert(response.status === 401 || response.status === 404, `Expected 401 Unauthorized or 404, got ${response.status}`);
171
- return {
172
- details: {
173
- status: response.status,
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
- * Helper: Complete a full SSO login flow through the mock server
180
- *
181
- * Returns the cookies set after successful authentication
182
- */
183
- async function completeLoginFlow(config, fetch) {
184
- try {
185
- // Step 1: Hit the login endpoint to get the authorization redirect
186
- const loginResponse = await fetch(config.loginPath);
187
- if (loginResponse.status !== 302) {
188
- return { cookies: new Map(), success: false, error: `Login did not redirect: ${loginResponse.status}` };
189
- }
190
- const authUrl = loginResponse.headers.get('Location');
191
- if (!authUrl) {
192
- return { cookies: new Map(), success: false, error: 'No redirect location from login' };
193
- }
194
- // Collect cookies from login response (includes state cookie)
195
- const loginCookies = parseCookies(loginResponse.headers);
196
- // Step 2: Follow the redirect to the authorization endpoint (mock server auto-authenticates)
197
- const authResponse = await globalThis.fetch(authUrl, { redirect: 'manual' });
198
- if (authResponse.status !== 302) {
199
- return { cookies: new Map(), success: false, error: `Auth did not redirect: ${authResponse.status}` };
200
- }
201
- const callbackUrl = authResponse.headers.get('Location');
202
- if (!callbackUrl) {
203
- return { cookies: new Map(), success: false, error: 'No redirect location from auth' };
204
- }
205
- // Step 3: Complete the callback with the authorization code
206
- // Extract just the path and query from the callback URL
207
- const callbackPath = new URL(callbackUrl).pathname + new URL(callbackUrl).search;
208
- const callbackResponse = await fetch(callbackPath, {
209
- headers: {
210
- Cookie: buildCookieHeader(loginCookies),
211
- },
212
- });
213
- // Should redirect to landing page on success
214
- if (callbackResponse.status !== 302) {
215
- return { cookies: new Map(), success: false, error: `Callback did not redirect: ${callbackResponse.status}` };
216
- }
217
- // Collect all cookies from the callback response
218
- const allCookies = parseCookies(callbackResponse.headers);
219
- return { cookies: allCookies, success: true };
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
- catch (error) {
222
- return { cookies: new Map(), success: false, error: error instanceof Error ? error.message : String(error) };
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
- * Test: JIT user provisioning works when enabled
227
- *
228
- * This test completes a full SSO flow and verifies the user is authenticated.
229
- * The actual JIT provisioning behavior depends on the server configuration.
230
- */
231
- async function testJitUserProvisioningFlow(config, fetch) {
232
- return runTest('SSO JIT User Provisioning Flow', async () => {
233
- // Complete the full login flow
234
- const { cookies, success, error } = await completeLoginFlow(config, fetch);
235
- assert(success, error || 'Login flow failed');
236
- // Verify we got authentication cookies
237
- const hasAuthCookies = Array.from(cookies.keys()).some((name) => name.includes('.access') || name.includes('.id'));
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
- * Runs all SSO validation tests
260
- */
261
- export async function validateSSO(config) {
262
- const startTime = performance.now();
263
- const mergedConfig = { ...DEFAULT_CONFIG, ...config };
264
- const fetch = createFetcher(mergedConfig);
265
- const tests = [];
266
- // Run all tests
267
- tests.push(await testLoginEndpoint(mergedConfig, fetch));
268
- tests.push(await testUserEndpointUnauthenticated(mergedConfig, fetch));
269
- tests.push(await testLogoutEndpoint(mergedConfig, fetch));
270
- tests.push(await testBackChannelLogoutEndpoint(mergedConfig, fetch));
271
- tests.push(await testCallbackEndpointInvalid(mergedConfig, fetch));
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
- suite: 'SSO',
281
- passed: failed === 0,
282
- tests,
283
- duration,
284
- summary: {
285
- total: tests.length,
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
- * Creates Vitest-compatible test suite for SSO validation
294
- */
295
- export function createSSOTests(config) {
296
- const mergedConfig = { ...DEFAULT_CONFIG, ...config };
297
- const fetch = createFetcher(mergedConfig);
298
- // Core/mandatory SSO tests
299
- const tests = [
300
- {
301
- name: 'login endpoint redirects to authorization URL',
302
- fn: async () => {
303
- const result = await testLoginEndpoint(mergedConfig, fetch);
304
- if (!result.passed)
305
- throw new Error(result.error);
306
- },
307
- },
308
- {
309
- name: 'user endpoint returns 401 when unauthenticated',
310
- fn: async () => {
311
- const result = await testUserEndpointUnauthenticated(mergedConfig, fetch);
312
- if (!result.passed)
313
- throw new Error(result.error);
314
- },
315
- },
316
- {
317
- name: 'callback endpoint rejects invalid requests',
318
- fn: async () => {
319
- const result = await testCallbackEndpointInvalid(mergedConfig, fetch);
320
- if (!result.passed)
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