@enterprisestandard/esv 0.0.5-beta.20260114.3 → 0.0.5-beta.20260115.2

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/sso/index.js CHANGED
@@ -1,449 +1,376 @@
1
- import { createRequire } from "node:module";
2
- var __create = Object.create;
3
- var __getProtoOf = Object.getPrototypeOf;
4
- var __defProp = Object.defineProperty;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __toESM = (mod, isNodeMode, target) => {
9
- target = mod != null ? __create(__getProtoOf(mod)) : {};
10
- const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
11
- for (let key of __getOwnPropNames(mod))
12
- if (!__hasOwnProp.call(to, key))
13
- __defProp(to, key, {
14
- get: () => mod[key],
15
- enumerable: true
16
- });
17
- return to;
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';
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',
18
19
  };
19
- var __moduleCache = /* @__PURE__ */ new WeakMap;
20
- var __toCommonJS = (from) => {
21
- var entry = __moduleCache.get(from), desc;
22
- if (entry)
23
- return entry;
24
- entry = __defProp({}, "__esModule", { value: true });
25
- if (from && typeof from === "object" || typeof from === "function")
26
- __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
27
- get: () => from[key],
28
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
29
- }));
30
- __moduleCache.set(from, entry);
31
- return entry;
32
- };
33
- var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
34
- var __export = (target, all) => {
35
- for (var name in all)
36
- __defProp(target, name, {
37
- get: all[name],
38
- enumerable: true,
39
- configurable: true,
40
- set: (newValue) => all[name] = () => newValue
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
+ };
41
55
  });
42
- };
43
- var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
44
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
45
-
46
- // packages/esv/src/utils.ts
47
- function createFetcher(config) {
48
- const timeout = config.timeout ?? 5000;
49
- const headers = config.headers ?? {};
50
- return async function fetcher(path, options = {}) {
51
- const url = `${config.baseUrl}${path}`;
52
- const controller = new AbortController;
53
- const timeoutId = setTimeout(() => controller.abort(), timeout);
54
- try {
55
- const response = await fetch(url, {
56
- ...options,
57
- signal: controller.signal,
58
- headers: {
59
- ...headers,
60
- ...options.headers
61
- },
62
- redirect: "manual"
63
- });
64
- return response;
65
- } finally {
66
- clearTimeout(timeoutId);
67
- }
68
- };
69
- }
70
- async function runTest(name, testFn) {
71
- const start = performance.now();
72
- try {
73
- const result = await testFn();
74
- return {
75
- name,
76
- passed: true,
77
- duration: performance.now() - start,
78
- details: result?.details
79
- };
80
- } catch (error) {
81
- return {
82
- name,
83
- passed: false,
84
- error: error instanceof Error ? error.message : String(error),
85
- duration: performance.now() - start
86
- };
87
- }
88
- }
89
- function skipTest(name, reason) {
90
- return {
91
- name,
92
- passed: true,
93
- duration: 0,
94
- details: { skipped: true, reason }
95
- };
96
56
  }
97
- function assert(condition, message) {
98
- if (!condition) {
99
- throw new Error(message);
100
- }
101
- }
102
- function assertEqual(actual, expected, message) {
103
- if (actual !== expected) {
104
- throw new Error(message ?? `Expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
105
- }
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
+ });
106
71
  }
107
- function assertValid(data, validator, message) {
108
- const result = validator["~standard"].validate(data);
109
- if (result instanceof Promise) {
110
- throw new Error(message ?? "Async validators are not supported in assertValid. Use the validator directly with await.");
111
- }
112
- if ("issues" in result) {
113
- const issues = result.issues;
114
- const errorMessages = issues.map((issue) => {
115
- const path = issue.path ? issue.path.map(String).join(".") : "";
116
- return path ? `${path}: ${issue.message}` : issue.message;
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
+ };
117
99
  });
118
- throw new Error(message ?? `Validation failed: ${errorMessages.join("; ")}`);
119
- }
120
100
  }
121
- function parseCookies(headers) {
122
- const cookies = new Map;
123
- const setCookieHeaders = headers.getSetCookie?.() ?? [];
124
- for (const cookie of setCookieHeaders) {
125
- const [pair] = cookie.split(";");
126
- const [name, value] = pair.split("=");
127
- if (name && value !== undefined) {
128
- cookies.set(name.trim(), value.trim());
129
- }
130
- }
131
- return cookies;
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
+ });
132
124
  }
133
- function buildCookieHeader(cookies) {
134
- return Array.from(cookies.entries()).map(([name, value]) => `${name}=${value}`).join("; ");
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
+ });
135
141
  }
136
-
137
- // packages/esv/src/sso/index.ts
138
- var exports_sso = {};
139
- __export(exports_sso, {
140
- validateSSO: () => validateSSO,
141
- createSSOTests: () => createSSOTests
142
- });
143
- async function testLoginEndpoint(config, fetch2) {
144
- return runTest("SSO Login Endpoint", async () => {
145
- const response = await fetch2(config.loginPath);
146
- assert(response.status === 302, `Expected 302 redirect, got ${response.status}`);
147
- const location = response.headers.get("Location");
148
- assert(location !== null, "Missing Location header in redirect");
149
- if (config.expectedAuthorizationUrlPattern) {
150
- assert(config.expectedAuthorizationUrlPattern.test(location), `Authorization URL does not match expected pattern: ${location}`);
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');
151
148
  }
152
- const url = new URL(location);
153
- assert(url.searchParams.has("client_id"), "Missing client_id in authorization URL");
154
- assert(url.searchParams.has("redirect_uri"), "Missing redirect_uri in authorization URL");
155
- assert(url.searchParams.has("response_type"), "Missing response_type in authorization URL");
156
- assert(url.searchParams.has("scope"), "Missing scope in authorization URL");
157
- assert(url.searchParams.has("state"), "Missing state in authorization URL");
158
- assert(url.searchParams.has("code_challenge"), "Missing code_challenge (PKCE) in authorization URL");
159
- assert(url.searchParams.has("code_challenge_method"), "Missing code_challenge_method (PKCE) in authorization URL");
160
- const cookies = parseCookies(response.headers);
161
- const hasStateCookie = Array.from(cookies.keys()).some((name) => name.includes(".state"));
162
- assert(hasStateCookie, "Missing state cookie in response");
163
- return {
164
- details: {
165
- authorizationUrl: location,
166
- pkceEnabled: true,
167
- stateParameter: url.searchParams.get("state")
168
- }
169
- };
170
- });
171
- }
172
- async function testUserEndpointUnauthenticated(config, fetch2) {
173
- return runTest("SSO User Endpoint (Unauthenticated)", async () => {
174
- const response = await fetch2(config.userPath);
175
- assert(response.status === 401, `Expected 401 Unauthorized, got ${response.status}`);
176
- return {
177
- details: {
178
- status: response.status
179
- }
180
- };
181
- });
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
+ });
182
159
  }
183
- async function testLogoutEndpoint(config, fetch2) {
184
- return runTest("SSO Logout Endpoint", async () => {
185
- const response = await fetch2(`${config.logoutPath}?redirect=/`);
186
- assert(response.status === 302 || response.status === 200, `Expected 302 or 200, got ${response.status}`);
187
- const cookies = parseCookies(response.headers);
188
- const clearedCookies = [];
189
- for (const [name, value] of cookies.entries()) {
190
- if (value === "" || value.includes("Max-Age=0")) {
191
- clearedCookies.push(name);
192
- }
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');
193
166
  }
194
- const setCookieHeaders = response.headers.getSetCookie?.() ?? [];
195
- const hasExpiredCookies = setCookieHeaders.some((cookie) => cookie.includes("Max-Age=0") || cookie.includes("expires=Thu, 01 Jan 1970"));
196
- assert(hasExpiredCookies || clearedCookies.length > 0 || setCookieHeaders.length === 0, "Logout should clear session cookies");
197
- return {
198
- details: {
199
- status: response.status,
200
- clearedCookies
201
- }
202
- };
203
- });
204
- }
205
- async function testBackChannelLogoutEndpoint(config, fetch2) {
206
- return runTest("SSO Back-Channel Logout Endpoint", async () => {
207
- const response = await fetch2(config.backChannelLogoutPath, {
208
- method: "POST",
209
- headers: {
210
- "Content-Type": "application/x-www-form-urlencoded"
211
- },
212
- body: ""
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
+ };
213
176
  });
214
- assert(response.status === 400 || response.status === 200 || response.status === 404, `Expected 400 or 200, got ${response.status}`);
215
- return {
216
- details: {
217
- status: response.status,
218
- endpointExists: response.status !== 404
219
- }
220
- };
221
- });
222
- }
223
- async function testCallbackEndpointInvalid(config, fetch2) {
224
- return runTest("SSO Callback Endpoint (Invalid)", async () => {
225
- const response = await fetch2(`${config.callbackPath}?code=invalid&state=invalid`);
226
- assert(response.status >= 400, `Expected error status for invalid callback, got ${response.status}`);
227
- return {
228
- details: {
229
- status: response.status,
230
- handlesInvalidCallback: true
231
- }
232
- };
233
- });
234
177
  }
235
- async function testTokenEndpointUnauthenticated(config, fetch2) {
236
- if (!config.tokenPath) {
237
- return skipTest("SSO Token Endpoint (Unauthenticated)", "Token path not configured");
238
- }
239
- return runTest("SSO Token Endpoint (Unauthenticated)", async () => {
240
- const response = await fetch2(config.tokenPath);
241
- assert(response.status === 401 || response.status === 404, `Expected 401 Unauthorized or 404, got ${response.status}`);
242
- return {
243
- details: {
244
- status: response.status
245
- }
246
- };
247
- });
248
- }
249
- async function testRefreshEndpointUnauthenticated(config, fetch2) {
250
- if (!config.refreshPath) {
251
- return skipTest("SSO Refresh Endpoint (Unauthenticated)", "Refresh path not configured");
252
- }
253
- return runTest("SSO Refresh Endpoint (Unauthenticated)", async () => {
254
- const response = await fetch2(config.refreshPath);
255
- assert(response.status === 401 || response.status === 404, `Expected 401 Unauthorized or 404, got ${response.status}`);
256
- return {
257
- details: {
258
- status: response.status
259
- }
260
- };
261
- });
262
- }
263
- async function completeLoginFlow(config, fetch2) {
264
- try {
265
- const loginResponse = await fetch2(config.loginPath);
266
- if (loginResponse.status !== 302) {
267
- return { cookies: new Map, success: false, error: `Login did not redirect: ${loginResponse.status}` };
268
- }
269
- const authUrl = loginResponse.headers.get("Location");
270
- if (!authUrl) {
271
- return { cookies: new Map, success: false, error: "No redirect location from login" };
272
- }
273
- const loginCookies = parseCookies(loginResponse.headers);
274
- const authResponse = await globalThis.fetch(authUrl, { redirect: "manual" });
275
- if (authResponse.status !== 302) {
276
- return { cookies: new Map, success: false, error: `Auth did not redirect: ${authResponse.status}` };
277
- }
278
- const callbackUrl = authResponse.headers.get("Location");
279
- if (!callbackUrl) {
280
- return { cookies: new Map, success: false, error: "No redirect location from auth" };
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 };
281
220
  }
282
- const callbackPath = new URL(callbackUrl).pathname + new URL(callbackUrl).search;
283
- const callbackResponse = await fetch2(callbackPath, {
284
- headers: {
285
- Cookie: buildCookieHeader(loginCookies)
286
- }
287
- });
288
- if (callbackResponse.status !== 302) {
289
- return { cookies: new Map, success: false, error: `Callback did not redirect: ${callbackResponse.status}` };
221
+ catch (error) {
222
+ return { cookies: new Map(), success: false, error: error instanceof Error ? error.message : String(error) };
290
223
  }
291
- const allCookies = parseCookies(callbackResponse.headers);
292
- return { cookies: allCookies, success: true };
293
- } catch (error) {
294
- return { cookies: new Map, success: false, error: error instanceof Error ? error.message : String(error) };
295
- }
296
224
  }
297
- async function testJitUserProvisioningFlow(config, fetch2) {
298
- return runTest("SSO JIT User Provisioning Flow", async () => {
299
- const { cookies, success, error } = await completeLoginFlow(config, fetch2);
300
- assert(success, error || "Login flow failed");
301
- const hasAuthCookies = Array.from(cookies.keys()).some((name) => name.includes(".access") || name.includes(".id"));
302
- assert(hasAuthCookies, "No authentication cookies set after login");
303
- const userResponse = await fetch2(config.userPath, {
304
- headers: {
305
- Cookie: buildCookieHeader(cookies)
306
- }
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
+ };
307
256
  });
308
- assert(userResponse.status === 200, `User endpoint returned ${userResponse.status} after login, expected 200`);
309
- const userData = await userResponse.json();
310
- assert(userData.id, "User data missing id field");
257
+ }
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;
311
279
  return {
312
- details: {
313
- userId: userData.id,
314
- userName: userData.userName,
315
- email: userData.email,
316
- jitFlowCompleted: true
317
- }
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
+ },
318
290
  };
319
- });
320
- }
321
- async function validateSSO(config) {
322
- const startTime = performance.now();
323
- const mergedConfig = { ...DEFAULT_CONFIG, ...config };
324
- const fetch2 = createFetcher(mergedConfig);
325
- const tests = [];
326
- tests.push(await testLoginEndpoint(mergedConfig, fetch2));
327
- tests.push(await testUserEndpointUnauthenticated(mergedConfig, fetch2));
328
- tests.push(await testLogoutEndpoint(mergedConfig, fetch2));
329
- tests.push(await testBackChannelLogoutEndpoint(mergedConfig, fetch2));
330
- tests.push(await testCallbackEndpointInvalid(mergedConfig, fetch2));
331
- tests.push(await testTokenEndpointUnauthenticated(mergedConfig, fetch2));
332
- tests.push(await testRefreshEndpointUnauthenticated(mergedConfig, fetch2));
333
- tests.push(await testJitUserProvisioningFlow(mergedConfig, fetch2));
334
- const duration = performance.now() - startTime;
335
- const passed = tests.filter((t) => t.passed).length;
336
- const failed = tests.filter((t) => !t.passed).length;
337
- const skipped = tests.filter((t) => t.details?.skipped).length;
338
- return {
339
- suite: "SSO",
340
- passed: failed === 0,
341
- tests,
342
- duration,
343
- summary: {
344
- total: tests.length,
345
- passed: passed - skipped,
346
- failed,
347
- skipped
348
- }
349
- };
350
291
  }
351
- function createSSOTests(config) {
352
- const mergedConfig = { ...DEFAULT_CONFIG, ...config };
353
- const fetch2 = createFetcher(mergedConfig);
354
- const tests = [
355
- {
356
- name: "login endpoint redirects to authorization URL",
357
- fn: async () => {
358
- const result = await testLoginEndpoint(mergedConfig, fetch2);
359
- if (!result.passed)
360
- throw new Error(result.error);
361
- }
362
- },
363
- {
364
- name: "user endpoint returns 401 when unauthenticated",
365
- fn: async () => {
366
- const result = await testUserEndpointUnauthenticated(mergedConfig, fetch2);
367
- if (!result.passed)
368
- throw new Error(result.error);
369
- }
370
- },
371
- {
372
- name: "callback endpoint rejects invalid requests",
373
- fn: async () => {
374
- const result = await testCallbackEndpointInvalid(mergedConfig, fetch2);
375
- if (!result.passed)
376
- throw new Error(result.error);
377
- }
378
- },
379
- {
380
- name: "token endpoint returns 401 when unauthenticated",
381
- fn: async () => {
382
- const result = await testTokenEndpointUnauthenticated(mergedConfig, fetch2);
383
- if (!result.passed)
384
- throw new Error(result.error);
385
- }
386
- },
387
- {
388
- name: "refresh endpoint returns 401 when unauthenticated",
389
- fn: async () => {
390
- const result = await testRefreshEndpointUnauthenticated(mergedConfig, fetch2);
391
- if (!result.passed)
392
- throw new Error(result.error);
393
- }
394
- }
395
- ];
396
- const ext = {
397
- createJITTests: () => [
398
- {
399
- name: "user provisioning flow completes successfully",
400
- fn: async () => {
401
- const result = await testJitUserProvisioningFlow(mergedConfig, fetch2);
402
- if (!result.passed)
403
- throw new Error(result.error);
404
- }
405
- }
406
- ],
407
- createLogoutTests: () => [
408
- {
409
- name: "logout endpoint clears cookies",
410
- fn: async () => {
411
- const result = await testLogoutEndpoint(mergedConfig, fetch2);
412
- if (!result.passed)
413
- throw new Error(result.error);
414
- }
415
- }
416
- ],
417
- createBackChannelLogoutTests: () => [
418
- {
419
- name: "endpoint exists",
420
- fn: async () => {
421
- const result = await testBackChannelLogoutEndpoint(mergedConfig, fetch2);
422
- if (!result.passed)
423
- throw new Error(result.error);
424
- }
425
- }
426
- ]
427
- };
428
- return { tests, ext };
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 };
429
375
  }
430
- var DEFAULT_CONFIG;
431
- var init_sso = __esm(() => {
432
- DEFAULT_CONFIG = {
433
- loginPath: "/api/auth/login",
434
- callbackPath: "/api/auth/callback",
435
- userPath: "/api/auth/user",
436
- logoutPath: "/api/auth/logout",
437
- backChannelLogoutPath: "/api/auth/logout/backchannel",
438
- tokenPath: "/api/auth/token",
439
- refreshPath: "/api/auth/refresh"
440
- };
441
- });
442
- init_sso();
443
-
444
- export {
445
- validateSSO,
446
- createSSOTests
447
- };
448
-
449
- //# debugId=65C413DE1A60173A64756E2164756E21
376
+ //# sourceMappingURL=index.js.map