@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/workload/index.js
CHANGED
|
@@ -1,433 +1,423 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (typeof value !== 'object' || value === null) {
|
|
17
|
-
return {
|
|
18
|
-
issues: [{ message: `Expected object, got ${typeof value}` }],
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
const token = value;
|
|
22
|
-
const issues = [];
|
|
23
|
-
if (typeof token.access_token !== 'string') {
|
|
24
|
-
issues.push({ message: 'Expected access_token to be string', path: ['access_token'] });
|
|
25
|
-
}
|
|
26
|
-
if (typeof token.token_type !== 'string') {
|
|
27
|
-
issues.push({ message: 'Expected token_type to be string', path: ['token_type'] });
|
|
28
|
-
}
|
|
29
|
-
// expires_in is optional but if present should be a number
|
|
30
|
-
if (token.expires_in !== undefined && typeof token.expires_in !== 'number') {
|
|
31
|
-
issues.push({ message: 'Expected expires_in to be number or undefined', path: ['expires_in'] });
|
|
32
|
-
}
|
|
33
|
-
// scope is optional but if present should be a string
|
|
34
|
-
if (token.scope !== undefined && typeof token.scope !== 'string') {
|
|
35
|
-
issues.push({ message: 'Expected scope to be string or undefined', path: ['scope'] });
|
|
36
|
-
}
|
|
37
|
-
if (issues.length > 0) {
|
|
38
|
-
return { issues };
|
|
39
|
-
}
|
|
40
|
-
return { value };
|
|
41
|
-
},
|
|
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
|
|
42
16
|
},
|
|
43
|
-
|
|
17
|
+
redirect: "manual"
|
|
18
|
+
// Don't follow redirects automatically
|
|
19
|
+
});
|
|
20
|
+
return response;
|
|
21
|
+
} finally {
|
|
22
|
+
clearTimeout(timeoutId);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
44
25
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
// JWKS keys have a standard structure - create a simple validator inline
|
|
50
|
-
// This validates the minimal required fields: kty and kid
|
|
26
|
+
async function runTest(name, testFn) {
|
|
27
|
+
const start = performance.now();
|
|
28
|
+
try {
|
|
29
|
+
const result = await testFn();
|
|
51
30
|
return {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
if (typeof key.kid !== 'string') {
|
|
65
|
-
issues.push({ message: 'Expected kid to be string', path: ['kid'] });
|
|
66
|
-
}
|
|
67
|
-
if (issues.length > 0) {
|
|
68
|
-
return { issues };
|
|
69
|
-
}
|
|
70
|
-
return { value };
|
|
71
|
-
},
|
|
72
|
-
},
|
|
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
|
|
73
42
|
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function skipTest(name, reason) {
|
|
46
|
+
return {
|
|
47
|
+
name,
|
|
48
|
+
passed: true,
|
|
49
|
+
duration: 0,
|
|
50
|
+
details: { skipped: true, reason }
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function assert(condition, message) {
|
|
54
|
+
if (!condition) {
|
|
55
|
+
throw new Error(message);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function assertValid(data, validator, message) {
|
|
59
|
+
const result = validator["~standard"].validate(data);
|
|
60
|
+
if (result instanceof Promise) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
message ?? "Async validators are not supported in assertValid. Use the validator directly with await."
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
if ("issues" in result) {
|
|
66
|
+
const issues = result.issues;
|
|
67
|
+
const errorMessages = issues.map((issue) => {
|
|
68
|
+
const path = issue.path ? issue.path.map(String).join(".") : "";
|
|
69
|
+
return path ? `${path}: ${issue.message}` : issue.message;
|
|
70
|
+
});
|
|
71
|
+
throw new Error(message ?? `Validation failed: ${errorMessages.join("; ")}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/workload/index.ts
|
|
76
|
+
function getWorkloadTokenResponseValidator() {
|
|
77
|
+
return {
|
|
78
|
+
"~standard": {
|
|
79
|
+
validate: (value) => {
|
|
80
|
+
if (typeof value !== "object" || value === null) {
|
|
81
|
+
return {
|
|
82
|
+
issues: [{ message: `Expected object, got ${typeof value}` }]
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const token = value;
|
|
86
|
+
const issues = [];
|
|
87
|
+
if (typeof token.access_token !== "string") {
|
|
88
|
+
issues.push({ message: "Expected access_token to be string", path: ["access_token"] });
|
|
89
|
+
}
|
|
90
|
+
if (typeof token.token_type !== "string") {
|
|
91
|
+
issues.push({ message: "Expected token_type to be string", path: ["token_type"] });
|
|
92
|
+
}
|
|
93
|
+
if (token.expires_in !== void 0 && typeof token.expires_in !== "number") {
|
|
94
|
+
issues.push({ message: "Expected expires_in to be number or undefined", path: ["expires_in"] });
|
|
95
|
+
}
|
|
96
|
+
if (token.scope !== void 0 && typeof token.scope !== "string") {
|
|
97
|
+
issues.push({ message: "Expected scope to be string or undefined", path: ["scope"] });
|
|
98
|
+
}
|
|
99
|
+
if (issues.length > 0) {
|
|
100
|
+
return { issues };
|
|
101
|
+
}
|
|
102
|
+
return { value };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function getJwksKeyValidator() {
|
|
108
|
+
return {
|
|
109
|
+
"~standard": {
|
|
110
|
+
validate: (value) => {
|
|
111
|
+
if (typeof value !== "object" || value === null) {
|
|
112
|
+
return {
|
|
113
|
+
issues: [{ message: `Expected object, got ${typeof value}` }]
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const key = value;
|
|
117
|
+
const issues = [];
|
|
118
|
+
if (typeof key.kty !== "string") {
|
|
119
|
+
issues.push({ message: "Expected kty to be string", path: ["kty"] });
|
|
120
|
+
}
|
|
121
|
+
if (typeof key.kid !== "string") {
|
|
122
|
+
issues.push({ message: "Expected kid to be string", path: ["kid"] });
|
|
123
|
+
}
|
|
124
|
+
if (issues.length > 0) {
|
|
125
|
+
return { issues };
|
|
126
|
+
}
|
|
127
|
+
return { value };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
};
|
|
74
131
|
}
|
|
75
|
-
/**
|
|
76
|
-
* Gets a token validation result validator (simple inline validator)
|
|
77
|
-
*/
|
|
78
132
|
function getTokenValidationResultValidator() {
|
|
133
|
+
return {
|
|
134
|
+
"~standard": {
|
|
135
|
+
validate: (value) => {
|
|
136
|
+
if (typeof value !== "object" || value === null) {
|
|
137
|
+
return {
|
|
138
|
+
issues: [{ message: `Expected object, got ${typeof value}` }]
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
const result = value;
|
|
142
|
+
const issues = [];
|
|
143
|
+
if (typeof result.valid !== "boolean") {
|
|
144
|
+
issues.push({ message: "Expected valid to be boolean", path: ["valid"] });
|
|
145
|
+
}
|
|
146
|
+
if (issues.length > 0) {
|
|
147
|
+
return { issues };
|
|
148
|
+
}
|
|
149
|
+
return { value };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
var DEFAULT_CONFIG = {
|
|
155
|
+
tokenPath: "/api/workload/token",
|
|
156
|
+
validatePath: "/api/workload/validate",
|
|
157
|
+
jwksPath: "/api/workload/jwks",
|
|
158
|
+
refreshPath: "/api/workload/refresh",
|
|
159
|
+
esvUrl: "http://localhost:3555"
|
|
160
|
+
};
|
|
161
|
+
async function testJwksEndpoint(config, fetch2) {
|
|
162
|
+
return runTest("Workload JWKS Endpoint", async () => {
|
|
163
|
+
const response = await fetch2(config.jwksPath);
|
|
164
|
+
assert(response.status === 200, `Expected 200 OK, got ${response.status}`);
|
|
165
|
+
const data = await response.json();
|
|
166
|
+
assert(Array.isArray(data.keys), "JWKS response missing keys array");
|
|
167
|
+
const keyValidator = getJwksKeyValidator();
|
|
168
|
+
for (const key of data.keys) {
|
|
169
|
+
assertValid(key, keyValidator);
|
|
170
|
+
}
|
|
79
171
|
return {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
issues: [{ message: `Expected object, got ${typeof value}` }],
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
const result = value;
|
|
88
|
-
const issues = [];
|
|
89
|
-
if (typeof result.valid !== 'boolean') {
|
|
90
|
-
issues.push({ message: 'Expected valid to be boolean', path: ['valid'] });
|
|
91
|
-
}
|
|
92
|
-
if (issues.length > 0) {
|
|
93
|
-
return { issues };
|
|
94
|
-
}
|
|
95
|
-
return { value };
|
|
96
|
-
},
|
|
97
|
-
},
|
|
172
|
+
details: {
|
|
173
|
+
keyCount: data.keys.length,
|
|
174
|
+
algorithms: data.keys.map((k) => k.alg).filter(Boolean)
|
|
175
|
+
}
|
|
98
176
|
};
|
|
177
|
+
});
|
|
99
178
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
};
|
|
110
|
-
/**
|
|
111
|
-
* Test: JWKS endpoint returns valid JWKS structure
|
|
112
|
-
*/
|
|
113
|
-
async function testJwksEndpoint(config, fetch) {
|
|
114
|
-
return runTest('Workload JWKS Endpoint', async () => {
|
|
115
|
-
const response = await fetch(config.jwksPath);
|
|
116
|
-
// Should return 200 OK
|
|
117
|
-
assert(response.status === 200, `Expected 200 OK, got ${response.status}`);
|
|
118
|
-
const data = await response.json();
|
|
119
|
-
// Should have JWKS structure
|
|
120
|
-
assert(Array.isArray(data.keys), 'JWKS response missing keys array');
|
|
121
|
-
// Each key should have required fields - validate using validator
|
|
122
|
-
const keyValidator = getJwksKeyValidator();
|
|
123
|
-
for (const key of data.keys) {
|
|
124
|
-
assertValid(key, keyValidator);
|
|
179
|
+
async function testTokenEndpoint(config, fetch2) {
|
|
180
|
+
const result = await runTest("Workload Token Endpoint", async () => {
|
|
181
|
+
const url = config.testScopes ? `${config.tokenPath}?scope=${encodeURIComponent(config.testScopes)}` : config.tokenPath;
|
|
182
|
+
const response = await fetch2(url);
|
|
183
|
+
if (response.status === 503) {
|
|
184
|
+
return {
|
|
185
|
+
details: {
|
|
186
|
+
skipped: true,
|
|
187
|
+
reason: "Workload authentication not configured"
|
|
125
188
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
assert(response.status === 200, `Expected 200 OK, got ${response.status}`);
|
|
192
|
+
const data = await response.json();
|
|
193
|
+
const validator = getWorkloadTokenResponseValidator();
|
|
194
|
+
assertValid(data, validator);
|
|
195
|
+
return {
|
|
196
|
+
details: {
|
|
197
|
+
tokenType: data.token_type,
|
|
198
|
+
hasToken: !!data.access_token,
|
|
199
|
+
token: data.access_token
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
});
|
|
203
|
+
return {
|
|
204
|
+
...result,
|
|
205
|
+
token: result.details?.token
|
|
206
|
+
};
|
|
133
207
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
: config.tokenPath;
|
|
142
|
-
const response = await fetch(url);
|
|
143
|
-
// Should return 200 OK (or 503 if workload not configured)
|
|
144
|
-
if (response.status === 503) {
|
|
145
|
-
return {
|
|
146
|
-
details: {
|
|
147
|
-
skipped: true,
|
|
148
|
-
reason: 'Workload authentication not configured',
|
|
149
|
-
},
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
assert(response.status === 200, `Expected 200 OK, got ${response.status}`);
|
|
153
|
-
const data = await response.json();
|
|
154
|
-
// Should have token response structure - validate using workload-specific validator
|
|
155
|
-
// (workload tokens don't have id_token, only access_token)
|
|
156
|
-
const validator = getWorkloadTokenResponseValidator();
|
|
157
|
-
assertValid(data, validator);
|
|
158
|
-
return {
|
|
159
|
-
details: {
|
|
160
|
-
tokenType: data.token_type,
|
|
161
|
-
hasToken: !!data.access_token,
|
|
162
|
-
token: data.access_token,
|
|
163
|
-
},
|
|
164
|
-
};
|
|
208
|
+
async function testValidateEndpoint(config, fetch2, token) {
|
|
209
|
+
return runTest("Workload Validate Endpoint", async () => {
|
|
210
|
+
const response = await fetch2(config.validatePath, {
|
|
211
|
+
method: "POST",
|
|
212
|
+
headers: {
|
|
213
|
+
Authorization: `Bearer ${token}`
|
|
214
|
+
}
|
|
165
215
|
});
|
|
216
|
+
assert(response.status === 200, `Expected 200 OK for valid token, got ${response.status}`);
|
|
217
|
+
const data = await response.json();
|
|
218
|
+
const validationResultValidator = getTokenValidationResultValidator();
|
|
219
|
+
assertValid(data, validationResultValidator);
|
|
220
|
+
assert(data.valid === true, "Token should be valid");
|
|
166
221
|
return {
|
|
167
|
-
|
|
168
|
-
|
|
222
|
+
details: {
|
|
223
|
+
valid: data.valid,
|
|
224
|
+
claims: data.claims
|
|
225
|
+
}
|
|
169
226
|
};
|
|
227
|
+
});
|
|
170
228
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
headers: {
|
|
179
|
-
Authorization: `Bearer ${token}`,
|
|
180
|
-
},
|
|
181
|
-
});
|
|
182
|
-
// Should return 200 OK for valid token
|
|
183
|
-
assert(response.status === 200, `Expected 200 OK for valid token, got ${response.status}`);
|
|
184
|
-
const data = await response.json();
|
|
185
|
-
// Should have validation result structure - validate using validator
|
|
186
|
-
const validationResultValidator = getTokenValidationResultValidator();
|
|
187
|
-
assertValid(data, validationResultValidator);
|
|
188
|
-
assert(data.valid === true, 'Token should be valid');
|
|
189
|
-
return {
|
|
190
|
-
details: {
|
|
191
|
-
valid: data.valid,
|
|
192
|
-
claims: data.claims,
|
|
193
|
-
},
|
|
194
|
-
};
|
|
229
|
+
async function testValidateEndpointInvalid(config, fetch2) {
|
|
230
|
+
return runTest("Workload Validate Endpoint (Invalid Token)", async () => {
|
|
231
|
+
const response = await fetch2(config.validatePath, {
|
|
232
|
+
method: "POST",
|
|
233
|
+
headers: {
|
|
234
|
+
Authorization: "Bearer invalid.token.here"
|
|
235
|
+
}
|
|
195
236
|
});
|
|
237
|
+
assert(response.status === 401, `Expected 401 Unauthorized for invalid token, got ${response.status}`);
|
|
238
|
+
const data = await response.json();
|
|
239
|
+
const validationResultValidator = getTokenValidationResultValidator();
|
|
240
|
+
assertValid(data, validationResultValidator);
|
|
241
|
+
assert(data.valid === false, "Invalid token should not validate");
|
|
242
|
+
return {
|
|
243
|
+
details: {
|
|
244
|
+
valid: data.valid,
|
|
245
|
+
error: data.error
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
});
|
|
196
249
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
return runTest('Workload Validate Endpoint (Invalid Token)', async () => {
|
|
202
|
-
const response = await fetch(config.validatePath, {
|
|
203
|
-
method: 'POST',
|
|
204
|
-
headers: {
|
|
205
|
-
Authorization: 'Bearer invalid.token.here',
|
|
206
|
-
},
|
|
207
|
-
});
|
|
208
|
-
// Should return 401 for invalid token
|
|
209
|
-
assert(response.status === 401, `Expected 401 Unauthorized for invalid token, got ${response.status}`);
|
|
210
|
-
const data = await response.json();
|
|
211
|
-
// Should indicate token is invalid - validate using validator
|
|
212
|
-
const validationResultValidator = getTokenValidationResultValidator();
|
|
213
|
-
assertValid(data, validationResultValidator);
|
|
214
|
-
assert(data.valid === false, 'Invalid token should not validate');
|
|
215
|
-
return {
|
|
216
|
-
details: {
|
|
217
|
-
valid: data.valid,
|
|
218
|
-
error: data.error,
|
|
219
|
-
},
|
|
220
|
-
};
|
|
250
|
+
async function testValidateEndpointNoAuth(config, fetch2) {
|
|
251
|
+
return runTest("Workload Validate Endpoint (No Auth)", async () => {
|
|
252
|
+
const response = await fetch2(config.validatePath, {
|
|
253
|
+
method: "POST"
|
|
221
254
|
});
|
|
255
|
+
assert(response.status === 401, `Expected 401 Unauthorized for missing auth, got ${response.status}`);
|
|
256
|
+
return {
|
|
257
|
+
details: {
|
|
258
|
+
status: response.status,
|
|
259
|
+
requiresAuth: true
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
});
|
|
222
263
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
return runTest('Workload Validate Endpoint (No Auth)', async () => {
|
|
228
|
-
const response = await fetch(config.validatePath, {
|
|
229
|
-
method: 'POST',
|
|
230
|
-
});
|
|
231
|
-
// Should return 401 for missing auth
|
|
232
|
-
assert(response.status === 401, `Expected 401 Unauthorized for missing auth, got ${response.status}`);
|
|
233
|
-
return {
|
|
234
|
-
details: {
|
|
235
|
-
status: response.status,
|
|
236
|
-
requiresAuth: true,
|
|
237
|
-
},
|
|
238
|
-
};
|
|
264
|
+
async function testRefreshEndpoint(config, fetch2) {
|
|
265
|
+
return runTest("Workload Refresh Endpoint", async () => {
|
|
266
|
+
const response = await fetch2(config.refreshPath, {
|
|
267
|
+
method: "POST"
|
|
239
268
|
});
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
return runTest('Workload Refresh Endpoint', async () => {
|
|
246
|
-
const response = await fetch(config.refreshPath, {
|
|
247
|
-
method: 'POST',
|
|
248
|
-
});
|
|
249
|
-
// Should return 200 OK or 503 if not configured
|
|
250
|
-
if (response.status === 503) {
|
|
251
|
-
return {
|
|
252
|
-
details: {
|
|
253
|
-
skipped: true,
|
|
254
|
-
reason: 'Workload authentication not configured',
|
|
255
|
-
},
|
|
256
|
-
};
|
|
269
|
+
if (response.status === 503) {
|
|
270
|
+
return {
|
|
271
|
+
details: {
|
|
272
|
+
skipped: true,
|
|
273
|
+
reason: "Workload authentication not configured"
|
|
257
274
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
assert(response.status === 200, `Expected 200 OK, got ${response.status}`);
|
|
278
|
+
const data = await response.json();
|
|
279
|
+
const validator = getWorkloadTokenResponseValidator();
|
|
280
|
+
assertValid(data, validator);
|
|
281
|
+
return {
|
|
282
|
+
details: {
|
|
283
|
+
tokenType: data.token_type,
|
|
284
|
+
hasToken: !!data.access_token
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
});
|
|
271
288
|
}
|
|
272
|
-
/**
|
|
273
|
-
* Test: Whoami endpoint with workload authentication
|
|
274
|
-
*
|
|
275
|
-
* This test calls the ESV mock server's /api/whoami endpoint to validate
|
|
276
|
-
* that the workload token issued by the application is valid and can be
|
|
277
|
-
* used for cross-service authentication. This allows each application to
|
|
278
|
-
* be tested independently without requiring other applications to be running.
|
|
279
|
-
*/
|
|
280
289
|
async function testWhoamiWithWorkload(config, token) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
},
|
|
288
|
-
});
|
|
289
|
-
// Should return 200 OK
|
|
290
|
-
if (response.status === 404) {
|
|
291
|
-
return {
|
|
292
|
-
details: {
|
|
293
|
-
skipped: true,
|
|
294
|
-
reason: 'ESV whoami endpoint not available',
|
|
295
|
-
},
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
assert(response.status === 200, `Expected 200 OK, got ${response.status}`);
|
|
299
|
-
const data = await response.json();
|
|
300
|
-
// Should have workload identity in response
|
|
301
|
-
assert(data.workload !== undefined, 'Response should include workload identity');
|
|
302
|
-
return {
|
|
303
|
-
details: {
|
|
304
|
-
workloadId: data.workload?.workload_id,
|
|
305
|
-
clientId: data.workload?.client_id,
|
|
306
|
-
},
|
|
307
|
-
};
|
|
290
|
+
return runTest("Workload Authentication (Whoami)", async () => {
|
|
291
|
+
const esvWhoamiUrl = `${config.esvUrl}/api/whoami`;
|
|
292
|
+
const response = await globalThis.fetch(esvWhoamiUrl, {
|
|
293
|
+
headers: {
|
|
294
|
+
Authorization: `Bearer ${token}`
|
|
295
|
+
}
|
|
308
296
|
});
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
const fetch = createFetcher(mergedConfig);
|
|
317
|
-
const tests = [];
|
|
318
|
-
// Test JWKS endpoint
|
|
319
|
-
tests.push(await testJwksEndpoint(mergedConfig, fetch));
|
|
320
|
-
// Test token endpoint and get a token for further tests
|
|
321
|
-
const tokenResult = await testTokenEndpoint(mergedConfig, fetch);
|
|
322
|
-
tests.push(tokenResult);
|
|
323
|
-
// Test validation endpoints
|
|
324
|
-
tests.push(await testValidateEndpointNoAuth(mergedConfig, fetch));
|
|
325
|
-
tests.push(await testValidateEndpointInvalid(mergedConfig, fetch));
|
|
326
|
-
// If we have a token, test validation with valid token
|
|
327
|
-
const token = tokenResult.token ?? config.validToken;
|
|
328
|
-
if (token) {
|
|
329
|
-
tests.push(await testValidateEndpoint(mergedConfig, fetch, token));
|
|
330
|
-
tests.push(await testWhoamiWithWorkload(mergedConfig, token));
|
|
331
|
-
}
|
|
332
|
-
else {
|
|
333
|
-
tests.push(skipTest('Workload Validate Endpoint', 'No valid token available'));
|
|
334
|
-
tests.push(skipTest('Workload Authentication (Whoami)', 'No valid token available'));
|
|
297
|
+
if (response.status === 404) {
|
|
298
|
+
return {
|
|
299
|
+
details: {
|
|
300
|
+
skipped: true,
|
|
301
|
+
reason: "ESV whoami endpoint not available"
|
|
302
|
+
}
|
|
303
|
+
};
|
|
335
304
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
const passed = tests.filter((t) => t.passed).length;
|
|
340
|
-
const failed = tests.filter((t) => !t.passed).length;
|
|
341
|
-
const skipped = tests.filter((t) => t.details?.skipped).length;
|
|
305
|
+
assert(response.status === 200, `Expected 200 OK, got ${response.status}`);
|
|
306
|
+
const data = await response.json();
|
|
307
|
+
assert(data.workload !== void 0, "Response should include workload identity");
|
|
342
308
|
return {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
summary: {
|
|
348
|
-
total: tests.length,
|
|
349
|
-
passed: passed - skipped,
|
|
350
|
-
failed,
|
|
351
|
-
skipped,
|
|
352
|
-
},
|
|
309
|
+
details: {
|
|
310
|
+
workloadId: data.workload?.workload_id,
|
|
311
|
+
clientId: data.workload?.client_id
|
|
312
|
+
}
|
|
353
313
|
};
|
|
314
|
+
});
|
|
354
315
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
316
|
+
async function validateWorkload(config) {
|
|
317
|
+
const startTime = performance.now();
|
|
318
|
+
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
319
|
+
const fetch2 = createFetcher(mergedConfig);
|
|
320
|
+
const tests = [];
|
|
321
|
+
tests.push(await testJwksEndpoint(mergedConfig, fetch2));
|
|
322
|
+
const tokenResult = await testTokenEndpoint(mergedConfig, fetch2);
|
|
323
|
+
tests.push(tokenResult);
|
|
324
|
+
tests.push(await testValidateEndpointNoAuth(mergedConfig, fetch2));
|
|
325
|
+
tests.push(await testValidateEndpointInvalid(mergedConfig, fetch2));
|
|
326
|
+
const token = tokenResult.token ?? config.validToken;
|
|
327
|
+
if (token) {
|
|
328
|
+
tests.push(await testValidateEndpoint(mergedConfig, fetch2, token));
|
|
329
|
+
tests.push(await testWhoamiWithWorkload(mergedConfig, token));
|
|
330
|
+
} else {
|
|
331
|
+
tests.push(skipTest("Workload Validate Endpoint", "No valid token available"));
|
|
332
|
+
tests.push(skipTest("Workload Authentication (Whoami)", "No valid token available"));
|
|
333
|
+
}
|
|
334
|
+
tests.push(await testRefreshEndpoint(mergedConfig, fetch2));
|
|
335
|
+
const duration = performance.now() - startTime;
|
|
336
|
+
const passed = tests.filter((t) => t.passed).length;
|
|
337
|
+
const failed = tests.filter((t) => !t.passed).length;
|
|
338
|
+
const skipped = tests.filter((t) => t.details?.skipped).length;
|
|
339
|
+
return {
|
|
340
|
+
suite: "Workload",
|
|
341
|
+
passed: failed === 0,
|
|
342
|
+
tests,
|
|
343
|
+
duration,
|
|
344
|
+
summary: {
|
|
345
|
+
total: tests.length,
|
|
346
|
+
passed: passed - skipped,
|
|
347
|
+
failed,
|
|
348
|
+
skipped
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
function createWorkloadTests(config) {
|
|
353
|
+
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
354
|
+
const fetch2 = createFetcher(mergedConfig);
|
|
355
|
+
let acquiredToken;
|
|
356
|
+
return [
|
|
357
|
+
{
|
|
358
|
+
name: "JWKS endpoint returns valid keys",
|
|
359
|
+
fn: async () => {
|
|
360
|
+
const result = await testJwksEndpoint(mergedConfig, fetch2);
|
|
361
|
+
if (!result.passed) throw new Error(result.error);
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
name: "token endpoint returns access token",
|
|
366
|
+
fn: async () => {
|
|
367
|
+
const result = await testTokenEndpoint(mergedConfig, fetch2);
|
|
368
|
+
if (!result.passed && !result.details?.skipped) throw new Error(result.error);
|
|
369
|
+
acquiredToken = result.token;
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
name: "validate endpoint rejects missing auth",
|
|
374
|
+
fn: async () => {
|
|
375
|
+
const result = await testValidateEndpointNoAuth(mergedConfig, fetch2);
|
|
376
|
+
if (!result.passed) throw new Error(result.error);
|
|
377
|
+
}
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
name: "validate endpoint rejects invalid tokens",
|
|
381
|
+
fn: async () => {
|
|
382
|
+
const result = await testValidateEndpointInvalid(mergedConfig, fetch2);
|
|
383
|
+
if (!result.passed) throw new Error(result.error);
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
name: "validate endpoint accepts valid token",
|
|
388
|
+
fn: async () => {
|
|
389
|
+
const token = acquiredToken ?? config.validToken;
|
|
390
|
+
if (!token) {
|
|
391
|
+
console.warn("Skipping: No valid token available");
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
const result = await testValidateEndpoint(mergedConfig, fetch2, token);
|
|
395
|
+
if (!result.passed) throw new Error(result.error);
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
name: "whoami endpoint works with workload authentication",
|
|
400
|
+
fn: async () => {
|
|
401
|
+
const token = acquiredToken ?? config.validToken;
|
|
402
|
+
if (!token) {
|
|
403
|
+
console.warn("Skipping: No valid token available");
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
const result = await testWhoamiWithWorkload(mergedConfig, token);
|
|
407
|
+
if (!result.passed && !result.details?.skipped) throw new Error(result.error);
|
|
408
|
+
}
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
name: "refresh endpoint works",
|
|
412
|
+
fn: async () => {
|
|
413
|
+
const result = await testRefreshEndpoint(mergedConfig, fetch2);
|
|
414
|
+
if (!result.passed && !result.details?.skipped) throw new Error(result.error);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
];
|
|
432
418
|
}
|
|
419
|
+
export {
|
|
420
|
+
createWorkloadTests,
|
|
421
|
+
validateWorkload
|
|
422
|
+
};
|
|
433
423
|
//# sourceMappingURL=index.js.map
|