@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.
@@ -1,1380 +1,34 @@
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;
18
- };
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
41
- });
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/server/crypto.ts
47
- import * as crypto from "node:crypto";
48
- var privateKey;
49
- var publicKey;
50
- var keyId;
51
- function initializeKeys() {
52
- const { publicKey: pubKey, privateKey: privKey } = crypto.generateKeyPairSync("rsa", {
53
- modulusLength: 2048
54
- });
55
- privateKey = privKey;
56
- publicKey = pubKey;
57
- keyId = crypto.randomBytes(8).toString("hex");
58
- }
59
- function getKeyId() {
60
- return keyId;
61
- }
62
- function base64UrlEncode(data) {
63
- const buffer = typeof data === "string" ? Buffer.from(data) : data;
64
- return buffer.toString("base64url");
65
- }
66
- function base64UrlDecode(data) {
67
- return Buffer.from(data, "base64url");
68
- }
69
- function signJwt(payload, expiresInSeconds = 3600) {
70
- const now = Math.floor(Date.now() / 1000);
71
- const header = {
72
- alg: "RS256",
73
- typ: "JWT",
74
- kid: keyId
75
- };
76
- const claims = {
77
- ...payload,
78
- iat: now,
79
- exp: now + expiresInSeconds
80
- };
81
- const encodedHeader = base64UrlEncode(JSON.stringify(header));
82
- const encodedPayload = base64UrlEncode(JSON.stringify(claims));
83
- const signatureInput = `${encodedHeader}.${encodedPayload}`;
84
- const sign = crypto.createSign("RSA-SHA256");
85
- sign.update(signatureInput);
86
- const signature = sign.sign(privateKey);
87
- return `${signatureInput}.${base64UrlEncode(signature)}`;
88
- }
89
- function verifyJwt(token) {
90
- try {
91
- const parts = token.split(".");
92
- if (parts.length !== 3) {
93
- return { valid: false, error: "Invalid JWT format" };
94
- }
95
- const [encodedHeader, encodedPayload, encodedSignature] = parts;
96
- const signatureInput = `${encodedHeader}.${encodedPayload}`;
97
- const signature = base64UrlDecode(encodedSignature);
98
- const verify = crypto.createVerify("RSA-SHA256");
99
- verify.update(signatureInput);
100
- const isValid = verify.verify(publicKey, signature);
101
- if (!isValid) {
102
- return { valid: false, error: "Invalid signature" };
103
- }
104
- const payload = JSON.parse(base64UrlDecode(encodedPayload).toString("utf-8"));
105
- const now = Math.floor(Date.now() / 1000);
106
- if (payload.exp && payload.exp < now) {
107
- return { valid: false, error: "Token expired" };
108
- }
109
- return { valid: true, payload };
110
- } catch (error) {
111
- return { valid: false, error: error instanceof Error ? error.message : "Unknown error" };
112
- }
113
- }
114
- function getJwks() {
115
- const jwk = publicKey.export({ format: "jwk" });
116
- return {
117
- keys: [
118
- {
119
- kty: jwk.kty,
120
- n: jwk.n,
121
- e: jwk.e,
122
- kid: keyId,
123
- use: "sig",
124
- alg: "RS256"
125
- }
126
- ]
127
- };
128
- }
129
- function generateRandomString(length = 32) {
130
- return crypto.randomBytes(length).toString("hex").substring(0, length);
131
- }
132
- function generateUUID() {
133
- return crypto.randomUUID();
134
- }
135
- function verifyCodeChallenge(codeVerifier, codeChallenge) {
136
- const hash = crypto.createHash("sha256").update(codeVerifier).digest();
137
- const computed = base64UrlEncode(hash);
138
- return computed === codeChallenge;
139
- }
140
- // packages/esv/src/server/server.ts
141
- import { createServer } from "node:http";
142
-
143
- // packages/esv/src/server/state.ts
144
- var users = new Map;
145
- var groups = new Map;
146
- var authCodes = new Map;
147
- var refreshTokens = new Map;
148
- var sessions = new Map;
149
- var defaultTestUser = {
150
- id: "test-user-001",
151
- userName: "testuser@example.com",
152
- email: "testuser@example.com",
153
- name: "Test User",
154
- givenName: "Test",
155
- familyName: "User"
156
- };
157
- function getUsers() {
158
- return Array.from(users.values());
159
- }
160
- function getUser(id) {
161
- return users.get(id);
162
- }
163
- function createUser(user) {
164
- const now = new Date().toISOString();
165
- user.meta = {
166
- resourceType: "User",
167
- created: now,
168
- lastModified: now,
169
- location: `/Users/${user.id}`
170
- };
171
- users.set(user.id, user);
172
- return user;
173
- }
174
- function updateUser(id, updates) {
175
- const user = users.get(id);
176
- if (!user)
177
- return;
178
- const updated = { ...user, ...updates };
179
- if (updated.meta) {
180
- updated.meta.lastModified = new Date().toISOString();
181
- }
182
- users.set(id, updated);
183
- return updated;
184
- }
185
- function deleteUser(id) {
186
- return users.delete(id);
187
- }
188
- function getGroups() {
189
- return Array.from(groups.values());
190
- }
191
- function getGroup(id) {
192
- return groups.get(id);
193
- }
194
- function createGroup(group) {
195
- const now = new Date().toISOString();
196
- group.meta = {
197
- resourceType: "Group",
198
- created: now,
199
- lastModified: now,
200
- location: `/Groups/${group.id}`
201
- };
202
- groups.set(group.id, group);
203
- return group;
204
- }
205
- function updateGroup(id, updates) {
206
- const group = groups.get(id);
207
- if (!group)
208
- return;
209
- const updated = { ...group, ...updates };
210
- if (updated.meta) {
211
- updated.meta.lastModified = new Date().toISOString();
212
- }
213
- groups.set(id, updated);
214
- return updated;
215
- }
216
- function deleteGroup(id) {
217
- return groups.delete(id);
218
- }
219
- function storeAuthCode(authCode) {
220
- authCodes.set(authCode.code, authCode);
221
- }
222
- function getAuthCode(code) {
223
- return authCodes.get(code);
224
- }
225
- function deleteAuthCode(code) {
226
- return authCodes.delete(code);
227
- }
228
- function storeRefreshToken(refreshToken) {
229
- refreshTokens.set(refreshToken.token, refreshToken);
230
- }
231
- function getRefreshToken(token) {
232
- return refreshTokens.get(token);
233
- }
234
- function deleteRefreshToken(token) {
235
- return refreshTokens.delete(token);
236
- }
237
- function deleteRefreshTokensBySession(sessionId) {
238
- for (const [token, data] of refreshTokens.entries()) {
239
- if (data.sessionId === sessionId) {
240
- refreshTokens.delete(token);
241
- }
242
- }
243
- }
244
- function createSession(session) {
245
- sessions.set(session.id, session);
246
- }
247
- function deleteSession(id) {
248
- deleteRefreshTokensBySession(id);
249
- return sessions.delete(id);
250
- }
251
- function getTestUser() {
252
- return defaultTestUser;
253
- }
254
- function setTestUser(user) {
255
- defaultTestUser = user;
256
- }
257
- function resetState() {
258
- users.clear();
259
- groups.clear();
260
- authCodes.clear();
261
- refreshTokens.clear();
262
- sessions.clear();
263
- defaultTestUser = {
264
- id: "test-user-001",
265
- userName: "testuser@example.com",
266
- email: "testuser@example.com",
267
- name: "Test User",
268
- givenName: "Test",
269
- familyName: "User"
270
- };
271
- }
272
-
273
- // packages/esv/src/server/iam.ts
274
- var SCIM_CONTENT_TYPE = "application/scim+json";
275
- async function parseJsonBody(req) {
276
- return new Promise((resolve, reject) => {
277
- let body = "";
278
- req.on("data", (chunk) => {
279
- body += chunk.toString();
280
- });
281
- req.on("end", () => {
282
- try {
283
- resolve(body ? JSON.parse(body) : {});
284
- } catch (error) {
285
- reject(error);
286
- }
287
- });
288
- req.on("error", reject);
289
- });
290
- }
291
- function validateAuth(req, res) {
292
- const auth = req.headers.authorization;
293
- if (!auth || !auth.startsWith("Bearer ")) {
294
- res.writeHead(401, { "Content-Type": SCIM_CONTENT_TYPE });
295
- res.end(JSON.stringify({
296
- schemas: ["urn:ietf:params:scim:api:messages:2.0:Error"],
297
- status: "401",
298
- detail: "Authorization required"
299
- }));
300
- return false;
301
- }
302
- return true;
303
- }
304
- function sendScimError(res, status, detail, scimType) {
305
- res.writeHead(status, { "Content-Type": SCIM_CONTENT_TYPE });
306
- res.end(JSON.stringify({
307
- schemas: ["urn:ietf:params:scim:api:messages:2.0:Error"],
308
- status: String(status),
309
- scimType,
310
- detail
311
- }));
312
- }
313
- function sendListResponse(res, resources, totalResults) {
314
- res.writeHead(200, { "Content-Type": SCIM_CONTENT_TYPE });
315
- res.end(JSON.stringify({
316
- schemas: ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
317
- totalResults: totalResults ?? resources.length,
318
- startIndex: 1,
319
- itemsPerPage: resources.length,
320
- Resources: resources
321
- }));
322
- }
323
- async function handleIamRequest(req, res, pathname) {
324
- const iamPath = pathname.replace(/^\/iam/, "");
325
- if (!validateAuth(req, res)) {
326
- return;
327
- }
328
- const usersMatch = iamPath.match(/^\/Users(?:\/([^/]+))?$/);
329
- const groupsMatch = iamPath.match(/^\/Groups(?:\/([^/]+))?$/);
330
- if (usersMatch) {
331
- const userId = usersMatch[1];
332
- await handleUsersRequest(req, res, userId);
333
- return;
334
- }
335
- if (groupsMatch) {
336
- const groupId = groupsMatch[1];
337
- await handleGroupsRequest(req, res, groupId);
338
- return;
339
- }
340
- sendScimError(res, 404, "Resource not found");
341
- }
342
- async function handleUsersRequest(req, res, userId) {
343
- if (userId) {
344
- switch (req.method) {
345
- case "GET":
346
- handleGetUser(res, userId);
347
- break;
348
- case "PUT":
349
- await handleReplaceUser(req, res, userId);
350
- break;
351
- case "PATCH":
352
- await handlePatchUser(req, res, userId);
353
- break;
354
- case "DELETE":
355
- handleDeleteUser(res, userId);
356
- break;
357
- default:
358
- sendScimError(res, 405, "Method not allowed");
359
- }
360
- } else {
361
- switch (req.method) {
362
- case "GET":
363
- handleListUsers(res);
364
- break;
365
- case "POST":
366
- await handleCreateUser(req, res);
367
- break;
368
- default:
369
- sendScimError(res, 405, "Method not allowed");
370
- }
371
- }
372
- }
373
- async function handleGroupsRequest(req, res, groupId) {
374
- if (groupId) {
375
- switch (req.method) {
376
- case "GET":
377
- handleGetGroup(res, groupId);
378
- break;
379
- case "PUT":
380
- await handleReplaceGroup(req, res, groupId);
381
- break;
382
- case "PATCH":
383
- await handlePatchGroup(req, res, groupId);
384
- break;
385
- case "DELETE":
386
- handleDeleteGroup(res, groupId);
387
- break;
388
- default:
389
- sendScimError(res, 405, "Method not allowed");
390
- }
391
- } else {
392
- switch (req.method) {
393
- case "GET":
394
- handleListGroups(res);
395
- break;
396
- case "POST":
397
- await handleCreateGroup(req, res);
398
- break;
399
- default:
400
- sendScimError(res, 405, "Method not allowed");
401
- }
402
- }
403
- }
404
- function handleListUsers(res) {
405
- const users2 = getUsers();
406
- sendListResponse(res, users2);
407
- }
408
- function handleGetUser(res, id) {
409
- const user = getUser(id);
410
- if (!user) {
411
- sendScimError(res, 404, `User ${id} not found`, "invalidValue");
412
- return;
413
- }
414
- res.writeHead(200, { "Content-Type": SCIM_CONTENT_TYPE });
415
- res.end(JSON.stringify(user));
416
- }
417
- async function handleCreateUser(req, res) {
418
- try {
419
- const body = await parseJsonBody(req);
420
- if (!body.userName) {
421
- sendScimError(res, 400, "userName is required", "invalidValue");
422
- return;
423
- }
424
- const user = {
425
- id: generateUUID(),
426
- schemas: body.schemas || [
427
- "urn:ietf:params:scim:schemas:core:2.0:User",
428
- "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
429
- ],
430
- userName: body.userName,
431
- displayName: body.displayName,
432
- name: body.name,
433
- emails: body.emails,
434
- active: body.active ?? true,
435
- externalId: body.externalId,
436
- groups: [],
437
- "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": body["urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"]
438
- };
439
- const created = createUser(user);
440
- res.writeHead(201, { "Content-Type": SCIM_CONTENT_TYPE });
441
- res.end(JSON.stringify(created));
442
- } catch (_error) {
443
- sendScimError(res, 400, "Invalid request body");
444
- }
445
- }
446
- async function handleReplaceUser(req, res, id) {
447
- const existing = getUser(id);
448
- if (!existing) {
449
- sendScimError(res, 404, `User ${id} not found`, "invalidValue");
450
- return;
451
- }
452
- try {
453
- const body = await parseJsonBody(req);
454
- const updated = updateUser(id, { ...body, id });
455
- if (updated) {
456
- res.writeHead(200, { "Content-Type": SCIM_CONTENT_TYPE });
457
- res.end(JSON.stringify(updated));
458
- } else {
459
- sendScimError(res, 404, `User ${id} not found`);
460
- }
461
- } catch (_error) {
462
- sendScimError(res, 400, "Invalid request body");
463
- }
464
- }
465
- async function handlePatchUser(req, res, id) {
466
- const existing = getUser(id);
467
- if (!existing) {
468
- sendScimError(res, 404, `User ${id} not found`, "invalidValue");
469
- return;
470
- }
471
- try {
472
- const body = await parseJsonBody(req);
473
- const operations = body.Operations || [];
474
- const updated = { ...existing };
475
- for (const op of operations) {
476
- if (op.op === "replace" && op.path && op.value !== undefined) {
477
- if (op.path === "displayName") {
478
- updated.displayName = op.value;
479
- } else if (op.path === "active") {
480
- updated.active = op.value;
481
- } else if (op.path.startsWith("name.")) {
482
- const namePart = op.path.split(".")[1];
483
- updated.name = { ...updated.name, [namePart]: op.value };
484
- }
485
- } else if (op.op === "add" && op.path && op.value !== undefined) {
486
- if (op.path === "emails") {
487
- updated.emails = [...updated.emails || [], ...op.value || []];
488
- }
489
- } else if (op.op === "remove" && op.path) {
490
- if (op.path === "displayName") {
491
- updated.displayName = undefined;
492
- }
493
- }
494
- }
495
- const result = updateUser(id, updated);
496
- if (result) {
497
- res.writeHead(200, { "Content-Type": SCIM_CONTENT_TYPE });
498
- res.end(JSON.stringify(result));
499
- } else {
500
- sendScimError(res, 404, `User ${id} not found`);
501
- }
502
- } catch (_error) {
503
- sendScimError(res, 400, "Invalid request body");
504
- }
505
- }
506
- function handleDeleteUser(res, id) {
507
- const deleted = deleteUser(id);
508
- if (!deleted) {
509
- sendScimError(res, 404, `User ${id} not found`, "invalidValue");
510
- return;
511
- }
512
- res.writeHead(204);
513
- res.end();
514
- }
515
- function handleListGroups(res) {
516
- const groups2 = getGroups();
517
- sendListResponse(res, groups2);
518
- }
519
- function handleGetGroup(res, id) {
520
- const group = getGroup(id);
521
- if (!group) {
522
- sendScimError(res, 404, `Group ${id} not found`, "invalidValue");
523
- return;
524
- }
525
- res.writeHead(200, { "Content-Type": SCIM_CONTENT_TYPE });
526
- res.end(JSON.stringify(group));
527
- }
528
- async function handleCreateGroup(req, res) {
529
- try {
530
- const body = await parseJsonBody(req);
531
- if (!body.displayName) {
532
- sendScimError(res, 400, "displayName is required", "invalidValue");
533
- return;
534
- }
535
- const group = {
536
- id: generateUUID(),
537
- schemas: body.schemas || ["urn:ietf:params:scim:schemas:core:2.0:Group"],
538
- displayName: body.displayName,
539
- externalId: body.externalId,
540
- members: body.members || []
541
- };
542
- const created = createGroup(group);
543
- res.writeHead(201, { "Content-Type": SCIM_CONTENT_TYPE });
544
- res.end(JSON.stringify(created));
545
- } catch (_error) {
546
- sendScimError(res, 400, "Invalid request body");
547
- }
548
- }
549
- async function handleReplaceGroup(req, res, id) {
550
- const existing = getGroup(id);
551
- if (!existing) {
552
- sendScimError(res, 404, `Group ${id} not found`, "invalidValue");
553
- return;
554
- }
555
- try {
556
- const body = await parseJsonBody(req);
557
- const updated = updateGroup(id, { ...body, id });
558
- if (updated) {
559
- res.writeHead(200, { "Content-Type": SCIM_CONTENT_TYPE });
560
- res.end(JSON.stringify(updated));
561
- } else {
562
- sendScimError(res, 404, `Group ${id} not found`);
563
- }
564
- } catch (_error) {
565
- sendScimError(res, 400, "Invalid request body");
566
- }
567
- }
568
- async function handlePatchGroup(req, res, id) {
569
- const existing = getGroup(id);
570
- if (!existing) {
571
- sendScimError(res, 404, `Group ${id} not found`, "invalidValue");
572
- return;
573
- }
574
- try {
575
- const body = await parseJsonBody(req);
576
- const operations = body.Operations || [];
577
- const updated = { ...existing };
578
- for (const op of operations) {
579
- if (op.op === "replace" && op.path && op.value !== undefined) {
580
- if (op.path === "displayName") {
581
- updated.displayName = op.value;
582
- }
583
- } else if (op.op === "add" && op.path && op.value !== undefined) {
584
- if (op.path === "members") {
585
- updated.members = [...updated.members || [], ...op.value || []];
586
- }
587
- } else if (op.op === "remove" && op.path) {
588
- if (op.path.startsWith("members[")) {
589
- const match = op.path.match(/members\[value eq "([^"]+)"\]/);
590
- if (match) {
591
- updated.members = (updated.members || []).filter((m) => m.value !== match[1]);
592
- }
593
- }
594
- }
595
- }
596
- const result = updateGroup(id, updated);
597
- if (result) {
598
- res.writeHead(200, { "Content-Type": SCIM_CONTENT_TYPE });
599
- res.end(JSON.stringify(result));
600
- } else {
601
- sendScimError(res, 404, `Group ${id} not found`);
602
- }
603
- } catch (_error) {
604
- sendScimError(res, 400, "Invalid request body");
605
- }
606
- }
607
- function handleDeleteGroup(res, id) {
608
- const deleted = deleteGroup(id);
609
- if (!deleted) {
610
- sendScimError(res, 404, `Group ${id} not found`, "invalidValue");
611
- return;
612
- }
613
- res.writeHead(204);
614
- res.end();
615
- }
616
-
617
- // packages/esv/src/server/sso.ts
618
- var ISSUER = "http://localhost:3555/sso";
619
- var TOKEN_EXPIRY = 3600;
620
- var REFRESH_EXPIRY = 86400;
621
- var CODE_EXPIRY = 300;
622
- async function parseFormBody(req) {
623
- return new Promise((resolve, reject) => {
624
- let body = "";
625
- req.on("data", (chunk) => {
626
- body += chunk.toString();
627
- });
628
- req.on("end", () => {
629
- resolve(new URLSearchParams(body));
630
- });
631
- req.on("error", reject);
632
- });
633
- }
634
- async function handleSsoRequest(req, res, pathname) {
635
- const ssoPath = pathname.replace(/^\/sso/, "");
636
- if (req.method === "GET" && ssoPath === "/authorize") {
637
- await handleAuthorize(req, res);
638
- return;
639
- }
640
- if (req.method === "POST" && ssoPath === "/token") {
641
- await handleToken(req, res);
642
- return;
643
- }
644
- if (req.method === "GET" && ssoPath === "/certs") {
645
- handleCerts(res);
646
- return;
647
- }
648
- if (req.method === "POST" && ssoPath === "/revoke") {
649
- await handleRevoke(req, res);
650
- return;
651
- }
652
- if (req.method === "GET" && ssoPath === "/userinfo") {
653
- handleUserInfo(req, res);
654
- return;
655
- }
656
- if (req.method === "GET" && ssoPath === "/logout") {
657
- handleLogout(req, res);
658
- return;
659
- }
660
- if (req.method === "POST" && ssoPath === "/logout/backchannel") {
661
- await handleBackChannelLogout(req, res);
662
- return;
663
- }
664
- if (req.method === "GET" && ssoPath === "/.well-known/openid-configuration") {
665
- handleOpenIDConfig(res);
666
- return;
667
- }
668
- res.writeHead(404, { "Content-Type": "application/json" });
669
- res.end(JSON.stringify({ error: "not_found" }));
670
- }
671
- async function handleAuthorize(req, res) {
672
- const url = new URL(req.url || "", `http://${req.headers.host}`);
673
- const params = url.searchParams;
674
- const clientId = params.get("client_id");
675
- const redirectUri = params.get("redirect_uri");
676
- const responseType = params.get("response_type");
677
- const scope = params.get("scope") || "openid";
678
- const state = params.get("state");
679
- const codeChallenge = params.get("code_challenge");
680
- const codeChallengeMethod = params.get("code_challenge_method");
681
- if (!clientId || !redirectUri || !responseType) {
682
- res.writeHead(400, { "Content-Type": "application/json" });
683
- res.end(JSON.stringify({
684
- error: "invalid_request",
685
- error_description: "Missing required parameters"
686
- }));
687
- return;
688
- }
689
- if (responseType !== "code") {
690
- res.writeHead(400, { "Content-Type": "application/json" });
691
- res.end(JSON.stringify({
692
- error: "unsupported_response_type",
693
- error_description: "Only code response type is supported"
694
- }));
695
- return;
696
- }
697
- const testUser = getTestUser();
698
- const code = generateRandomString(32);
699
- const authCode = {
700
- code,
701
- userId: testUser.id,
702
- clientId,
703
- redirectUri,
704
- scope,
705
- codeChallenge: codeChallenge || undefined,
706
- codeChallengeMethod: codeChallengeMethod || undefined,
707
- state: state || undefined,
708
- createdAt: new Date,
709
- expiresAt: new Date(Date.now() + CODE_EXPIRY * 1000)
710
- };
711
- storeAuthCode(authCode);
712
- const redirectUrl = new URL(redirectUri);
713
- redirectUrl.searchParams.set("code", code);
714
- if (state) {
715
- redirectUrl.searchParams.set("state", state);
716
- }
717
- res.writeHead(302, { Location: redirectUrl.toString() });
718
- res.end();
719
- }
720
- async function handleToken(req, res) {
721
- const body = await parseFormBody(req);
722
- const grantType = body.get("grant_type");
723
- if (grantType === "authorization_code") {
724
- await handleAuthorizationCodeGrant(body, res);
725
- } else if (grantType === "refresh_token") {
726
- await handleRefreshTokenGrant(body, res);
727
- } else {
728
- res.writeHead(400, { "Content-Type": "application/json" });
729
- res.end(JSON.stringify({
730
- error: "unsupported_grant_type",
731
- error_description: "Only authorization_code and refresh_token are supported"
732
- }));
733
- }
734
- }
735
- async function handleAuthorizationCodeGrant(body, res) {
736
- const code = body.get("code");
737
- const redirectUri = body.get("redirect_uri");
738
- const codeVerifier = body.get("code_verifier");
739
- if (!code || !redirectUri) {
740
- res.writeHead(400, { "Content-Type": "application/json" });
741
- res.end(JSON.stringify({
742
- error: "invalid_request",
743
- error_description: "Missing code or redirect_uri"
744
- }));
745
- return;
746
- }
747
- const authCode = getAuthCode(code);
748
- if (!authCode) {
749
- res.writeHead(400, { "Content-Type": "application/json" });
750
- res.end(JSON.stringify({
751
- error: "invalid_grant",
752
- error_description: "Invalid or expired authorization code"
753
- }));
754
- return;
755
- }
756
- if (authCode.redirectUri !== redirectUri) {
757
- res.writeHead(400, { "Content-Type": "application/json" });
758
- res.end(JSON.stringify({
759
- error: "invalid_grant",
760
- error_description: "Redirect URI mismatch"
761
- }));
762
- return;
763
- }
764
- if (authCode.codeChallenge && authCode.codeChallengeMethod === "S256") {
765
- if (!codeVerifier || !verifyCodeChallenge(codeVerifier, authCode.codeChallenge)) {
766
- res.writeHead(400, { "Content-Type": "application/json" });
767
- res.end(JSON.stringify({
768
- error: "invalid_grant",
769
- error_description: "Invalid code verifier"
770
- }));
771
- return;
772
- }
773
- }
774
- deleteAuthCode(code);
775
- const sessionId = generateUUID();
776
- createSession({
777
- id: sessionId,
778
- userId: authCode.userId,
779
- createdAt: new Date,
780
- lastActivityAt: new Date
781
- });
782
- const testUser = getTestUser();
783
- const tokens = generateTokens(testUser, authCode.clientId, authCode.scope, sessionId);
784
- res.writeHead(200, { "Content-Type": "application/json" });
785
- res.end(JSON.stringify(tokens));
786
- }
787
- async function handleRefreshTokenGrant(body, res) {
788
- const refreshToken = body.get("refresh_token");
789
- if (!refreshToken) {
790
- res.writeHead(400, { "Content-Type": "application/json" });
791
- res.end(JSON.stringify({
792
- error: "invalid_request",
793
- error_description: "Missing refresh_token"
794
- }));
795
- return;
796
- }
797
- const tokenData = getRefreshToken(refreshToken);
798
- if (!tokenData || tokenData.expiresAt < new Date) {
799
- res.writeHead(400, { "Content-Type": "application/json" });
800
- res.end(JSON.stringify({
801
- error: "invalid_grant",
802
- error_description: "Invalid or expired refresh token"
803
- }));
804
- return;
805
- }
806
- deleteRefreshToken(refreshToken);
807
- const testUser = getTestUser();
808
- const tokens = generateTokens(testUser, tokenData.clientId, tokenData.scope, tokenData.sessionId);
809
- res.writeHead(200, { "Content-Type": "application/json" });
810
- res.end(JSON.stringify(tokens));
811
- }
812
- function generateTokens(user, clientId, scope, sessionId) {
813
- const now = Math.floor(Date.now() / 1000);
814
- const idTokenClaims = {
815
- iss: ISSUER,
816
- sub: user.id,
817
- aud: clientId,
818
- nonce: generateRandomString(16),
819
- sid: sessionId,
820
- auth_time: now,
821
- email: user.email,
822
- email_verified: true,
823
- name: user.name,
824
- given_name: user.givenName,
825
- family_name: user.familyName,
826
- preferred_username: user.userName,
827
- picture: user.picture
828
- };
829
- const accessTokenClaims = {
830
- iss: ISSUER,
831
- sub: user.id,
832
- aud: clientId,
833
- scope,
834
- sid: sessionId
835
- };
836
- const idToken = signJwt(idTokenClaims, TOKEN_EXPIRY);
837
- const accessToken = signJwt(accessTokenClaims, TOKEN_EXPIRY);
838
- const refreshTokenValue = generateRandomString(64);
839
- const refreshToken = {
840
- token: refreshTokenValue,
841
- userId: user.id,
842
- clientId,
843
- scope,
844
- sessionId,
845
- createdAt: new Date,
846
- expiresAt: new Date(Date.now() + REFRESH_EXPIRY * 1000)
847
- };
848
- storeRefreshToken(refreshToken);
849
- return {
850
- access_token: accessToken,
851
- token_type: "Bearer",
852
- expires_in: TOKEN_EXPIRY,
853
- refresh_token: refreshTokenValue,
854
- refresh_expires_in: REFRESH_EXPIRY,
855
- id_token: idToken,
856
- scope,
857
- session_state: sessionId
858
- };
859
- }
860
- function handleCerts(res) {
861
- const jwks = getJwks();
862
- res.writeHead(200, { "Content-Type": "application/json" });
863
- res.end(JSON.stringify(jwks));
864
- }
865
- async function handleRevoke(req, res) {
866
- const body = await parseFormBody(req);
867
- const token = body.get("token");
868
- const tokenTypeHint = body.get("token_type_hint");
869
- if (token) {
870
- if (tokenTypeHint === "refresh_token" || !tokenTypeHint) {
871
- deleteRefreshToken(token);
872
- }
873
- }
874
- res.writeHead(200, { "Content-Type": "application/json" });
875
- res.end(JSON.stringify({ success: true }));
876
- }
877
- function handleUserInfo(req, res) {
878
- const authHeader = req.headers.authorization;
879
- if (!authHeader?.startsWith("Bearer ")) {
880
- res.writeHead(401, { "Content-Type": "application/json" });
881
- res.end(JSON.stringify({ error: "invalid_token" }));
882
- return;
883
- }
884
- const user = getTestUser();
885
- res.writeHead(200, { "Content-Type": "application/json" });
886
- res.end(JSON.stringify({
887
- sub: user.id,
888
- email: user.email,
889
- email_verified: true,
890
- name: user.name,
891
- given_name: user.givenName,
892
- family_name: user.familyName,
893
- preferred_username: user.userName,
894
- picture: user.picture
895
- }));
896
- }
897
- function handleLogout(req, res) {
898
- const url = new URL(req.url || "", `http://${req.headers.host}`);
899
- const postLogoutRedirectUri = url.searchParams.get("post_logout_redirect_uri");
900
- if (postLogoutRedirectUri) {
901
- res.writeHead(302, { Location: postLogoutRedirectUri });
902
- res.end();
903
- } else {
904
- res.writeHead(200, { "Content-Type": "text/html" });
905
- res.end("<html><body><h1>Logged out</h1></body></html>");
906
- }
907
- }
908
- async function handleBackChannelLogout(req, res) {
909
- const body = await parseFormBody(req);
910
- const logoutToken = body.get("logout_token");
911
- if (!logoutToken) {
912
- res.writeHead(400, { "Content-Type": "application/json" });
913
- res.end(JSON.stringify({ error: "invalid_request", error_description: "Missing logout_token" }));
914
- return;
915
- }
916
- try {
917
- const parts = logoutToken.split(".");
918
- if (parts.length >= 2) {
919
- const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf-8"));
920
- if (payload.sid) {
921
- deleteSession(payload.sid);
922
- }
923
- }
924
- } catch {}
925
- res.writeHead(200, { "Content-Type": "application/json" });
926
- res.end(JSON.stringify({ success: true }));
927
- }
928
- function handleOpenIDConfig(res) {
929
- res.writeHead(200, { "Content-Type": "application/json" });
930
- res.end(JSON.stringify({
931
- issuer: ISSUER,
932
- authorization_endpoint: `${ISSUER}/authorize`,
933
- token_endpoint: `${ISSUER}/token`,
934
- userinfo_endpoint: `${ISSUER}/userinfo`,
935
- jwks_uri: `${ISSUER}/certs`,
936
- end_session_endpoint: `${ISSUER}/logout`,
937
- revocation_endpoint: `${ISSUER}/revoke`,
938
- response_types_supported: ["code"],
939
- subject_types_supported: ["public"],
940
- id_token_signing_alg_values_supported: ["RS256"],
941
- scopes_supported: ["openid", "profile", "email"],
942
- token_endpoint_auth_methods_supported: ["client_secret_post", "client_secret_basic"],
943
- claims_supported: [
944
- "sub",
945
- "iss",
946
- "aud",
947
- "exp",
948
- "iat",
949
- "nonce",
950
- "email",
951
- "email_verified",
952
- "name",
953
- "given_name",
954
- "family_name",
955
- "preferred_username",
956
- "picture",
957
- "sid"
958
- ],
959
- code_challenge_methods_supported: ["S256"],
960
- backchannel_logout_supported: true,
961
- backchannel_logout_session_supported: true
962
- }));
963
- }
964
-
965
- // packages/esv/src/server/vault.ts
966
- function getEsvConfig(baseUrl = "http://localhost:3555") {
967
- return {
968
- sso: {
969
- authority: `${baseUrl}/sso`,
970
- token_url: `${baseUrl}/sso/token`,
971
- authorization_url: `${baseUrl}/sso/authorize`,
972
- jwks_uri: `${baseUrl}/sso/certs`,
973
- client_id: "local-test-client",
974
- client_secret: "local-test-secret",
975
- redirect_uri: "http://localhost:3000/api/auth/callback",
976
- scope: "openid profile email",
977
- revocation_endpoint: `${baseUrl}/sso/revoke`,
978
- end_session_endpoint: `${baseUrl}/sso/logout`
979
- },
980
- iam: {
981
- url: `${baseUrl}/iam`
982
- },
983
- workload: {
984
- token_url: `${baseUrl}/workload/token`,
985
- jwks_uri: `${baseUrl}/workload/certs`,
986
- client_id: "local-workload-client",
987
- client_secret: "local-workload-secret",
988
- issuer: `${baseUrl}/workload`,
989
- audience: `${baseUrl}/workload`
990
- }
991
- };
992
- }
993
- function handleVaultRequest(req, res, pathname) {
994
- const vaultPath = pathname.replace(/^\/vault/, "");
995
- if (req.method === "GET" && vaultPath === "/v1/secret/data/esv/config") {
996
- const token = req.headers["x-vault-token"];
997
- if (token !== "local-esv-token") {
998
- res.writeHead(403, { "Content-Type": "application/json" });
999
- res.end(JSON.stringify({ errors: ["permission denied"] }));
1000
- return;
1001
- }
1002
- const config = getEsvConfig();
1003
- res.writeHead(200, { "Content-Type": "application/json" });
1004
- res.end(JSON.stringify({
1005
- request_id: "local-esv-request",
1006
- lease_id: "",
1007
- renewable: false,
1008
- lease_duration: 0,
1009
- data: {
1010
- data: config,
1011
- metadata: {
1012
- created_time: new Date().toISOString(),
1013
- deletion_time: "",
1014
- destroyed: false,
1015
- version: 1
1016
- }
1017
- },
1018
- wrap_info: null,
1019
- warnings: null,
1020
- auth: null
1021
- }));
1022
- return;
1023
- }
1024
- if (req.method === "GET" && vaultPath.startsWith("/v1/secret/data/")) {
1025
- const token = req.headers["x-vault-token"];
1026
- if (token !== "local-esv-token") {
1027
- res.writeHead(403, { "Content-Type": "application/json" });
1028
- res.end(JSON.stringify({ errors: ["permission denied"] }));
1029
- return;
1030
- }
1031
- res.writeHead(404, { "Content-Type": "application/json" });
1032
- res.end(JSON.stringify({ errors: ["secret not found"] }));
1033
- return;
1034
- }
1035
- res.writeHead(404, { "Content-Type": "application/json" });
1036
- res.end(JSON.stringify({ errors: ["path not found"] }));
1037
- }
1038
-
1039
- // packages/esv/src/server/workload.ts
1040
- var ISSUER2 = "http://localhost:3555/workload";
1041
- var TOKEN_EXPIRY2 = 3600;
1042
- var VALID_CLIENTS = {
1043
- "local-workload-client": "local-workload-secret"
1044
- };
1045
- async function parseFormBody2(req) {
1046
- return new Promise((resolve, reject) => {
1047
- let body = "";
1048
- req.on("data", (chunk) => {
1049
- body += chunk.toString();
1050
- });
1051
- req.on("end", () => {
1052
- resolve(new URLSearchParams(body));
1053
- });
1054
- req.on("error", reject);
1055
- });
1056
- }
1057
- async function handleWorkloadRequest(req, res, pathname) {
1058
- const workloadPath = pathname.replace(/^\/workload/, "");
1059
- if (req.method === "POST" && workloadPath === "/token") {
1060
- await handleToken2(req, res);
1061
- return;
1062
- }
1063
- if (req.method === "GET" && workloadPath === "/certs") {
1064
- handleCerts2(res);
1065
- return;
1066
- }
1067
- if (req.method === "POST" && workloadPath === "/validate") {
1068
- handleValidate(req, res);
1069
- return;
1070
- }
1071
- res.writeHead(404, { "Content-Type": "application/json" });
1072
- res.end(JSON.stringify({ error: "not_found" }));
1073
- }
1074
- async function handleToken2(req, res) {
1075
- const body = await parseFormBody2(req);
1076
- const grantType = body.get("grant_type");
1077
- if (grantType === "client_credentials") {
1078
- handleClientCredentials(body, res);
1079
- } else if (grantType === "urn:ietf:params:oauth:grant-type:jwt-bearer") {
1080
- handleJwtBearer(body, res);
1081
- } else {
1082
- res.writeHead(400, { "Content-Type": "application/json" });
1083
- res.end(JSON.stringify({
1084
- error: "unsupported_grant_type",
1085
- error_description: "Only client_credentials and jwt-bearer are supported"
1086
- }));
1087
- }
1088
- }
1089
- function handleClientCredentials(body, res) {
1090
- const clientId = body.get("client_id");
1091
- const clientSecret = body.get("client_secret");
1092
- const scope = body.get("scope") || "";
1093
- if (!clientId || !clientSecret) {
1094
- res.writeHead(400, { "Content-Type": "application/json" });
1095
- res.end(JSON.stringify({
1096
- error: "invalid_request",
1097
- error_description: "Missing client_id or client_secret"
1098
- }));
1099
- return;
1100
- }
1101
- const expectedSecret = VALID_CLIENTS[clientId];
1102
- if (!expectedSecret || expectedSecret !== clientSecret) {
1103
- res.writeHead(401, { "Content-Type": "application/json" });
1104
- res.end(JSON.stringify({
1105
- error: "invalid_client",
1106
- error_description: "Invalid client credentials"
1107
- }));
1108
- return;
1109
- }
1110
- const accessTokenClaims = {
1111
- iss: ISSUER2,
1112
- sub: clientId,
1113
- aud: ISSUER2,
1114
- client_id: clientId,
1115
- scope
1116
- };
1117
- const accessToken = signJwt(accessTokenClaims, TOKEN_EXPIRY2);
1118
- res.writeHead(200, { "Content-Type": "application/json" });
1119
- res.end(JSON.stringify({
1120
- access_token: accessToken,
1121
- token_type: "Bearer",
1122
- expires_in: TOKEN_EXPIRY2,
1123
- scope
1124
- }));
1125
- }
1126
- function handleJwtBearer(body, res) {
1127
- const assertion = body.get("assertion");
1128
- const scope = body.get("scope") || "";
1129
- if (!assertion) {
1130
- res.writeHead(400, { "Content-Type": "application/json" });
1131
- res.end(JSON.stringify({
1132
- error: "invalid_request",
1133
- error_description: "Missing assertion"
1134
- }));
1135
- return;
1136
- }
1137
- const result = verifyJwt(assertion);
1138
- if (!result.valid) {
1139
- res.writeHead(400, { "Content-Type": "application/json" });
1140
- res.end(JSON.stringify({
1141
- error: "invalid_grant",
1142
- error_description: result.error || "Invalid JWT assertion"
1143
- }));
1144
- return;
1145
- }
1146
- const workloadId = result.payload?.sub;
1147
- const accessTokenClaims = {
1148
- iss: ISSUER2,
1149
- sub: workloadId,
1150
- aud: ISSUER2,
1151
- workload_id: workloadId,
1152
- scope: scope || result.payload?.scope || ""
1153
- };
1154
- const accessToken = signJwt(accessTokenClaims, TOKEN_EXPIRY2);
1155
- res.writeHead(200, { "Content-Type": "application/json" });
1156
- res.end(JSON.stringify({
1157
- access_token: accessToken,
1158
- token_type: "Bearer",
1159
- expires_in: TOKEN_EXPIRY2,
1160
- scope: accessTokenClaims.scope
1161
- }));
1162
- }
1163
- function handleCerts2(res) {
1164
- const jwks = getJwks();
1165
- res.writeHead(200, { "Content-Type": "application/json" });
1166
- res.end(JSON.stringify(jwks));
1167
- }
1168
- function handleValidate(req, res) {
1169
- const authHeader = req.headers.authorization;
1170
- if (!authHeader || !authHeader.startsWith("Bearer ")) {
1171
- res.writeHead(401, { "Content-Type": "application/json" });
1172
- res.end(JSON.stringify({
1173
- valid: false,
1174
- error: "Missing Authorization header"
1175
- }));
1176
- return;
1177
- }
1178
- const token = authHeader.substring(7);
1179
- const result = verifyJwt(token);
1180
- if (!result.valid) {
1181
- res.writeHead(401, { "Content-Type": "application/json" });
1182
- res.end(JSON.stringify({
1183
- valid: false,
1184
- error: result.error
1185
- }));
1186
- return;
1187
- }
1188
- res.writeHead(200, { "Content-Type": "application/json" });
1189
- res.end(JSON.stringify({
1190
- valid: true,
1191
- claims: result.payload,
1192
- expiresAt: result.payload?.exp ? new Date(result.payload.exp * 1000).toISOString() : undefined
1193
- }));
1194
- }
1195
- function handleWhoamiRequest(req, res) {
1196
- const authHeader = req.headers.authorization;
1197
- if (!authHeader || !authHeader.startsWith("Bearer ")) {
1198
- res.writeHead(401, { "Content-Type": "application/json" });
1199
- res.end(JSON.stringify({ error: "Unauthorized" }));
1200
- return;
1201
- }
1202
- const token = authHeader.substring(7);
1203
- const result = verifyJwt(token);
1204
- if (!result.valid) {
1205
- res.writeHead(401, { "Content-Type": "application/json" });
1206
- res.end(JSON.stringify({ error: "Invalid token", details: result.error }));
1207
- return;
1208
- }
1209
- res.writeHead(200, { "Content-Type": "application/json" });
1210
- res.end(JSON.stringify({
1211
- user: null,
1212
- workload: {
1213
- workload_id: result.payload?.sub,
1214
- client_id: result.payload?.client_id,
1215
- scope: result.payload?.scope
1216
- }
1217
- }));
1218
- }
1219
-
1220
- // packages/esv/src/server/server.ts
1221
- async function handleWebhook(req, res) {
1222
- if (req.method !== "POST") {
1223
- res.writeHead(405, { "Content-Type": "application/json" });
1224
- res.end(JSON.stringify({ error: "Method not allowed" }));
1225
- return;
1226
- }
1227
- let body = "";
1228
- req.on("data", (chunk) => {
1229
- body += chunk.toString();
1230
- });
1231
- req.on("end", () => {
1232
- try {
1233
- const payload = JSON.parse(body);
1234
- res.writeHead(200, { "Content-Type": "application/json" });
1235
- res.end(JSON.stringify({ received: true, payload }));
1236
- } catch (error) {
1237
- res.writeHead(400, { "Content-Type": "application/json" });
1238
- res.end(JSON.stringify({ error: "Invalid JSON" }));
1239
- }
1240
- });
1241
- }
1242
- var DEFAULT_PORT = 3555;
1243
- var DEFAULT_HOST = "localhost";
1244
- var server = null;
1245
- var isStarted = false;
1246
- async function handleRequest(req, res, verbose) {
1247
- const url = new URL(req.url || "/", `http://${req.headers.host}`);
1248
- const pathname = url.pathname;
1249
- if (verbose) {
1250
- console.log(`[ESV Server] ${req.method} ${pathname}`);
1251
- }
1252
- res.setHeader("Access-Control-Allow-Origin", "*");
1253
- res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS");
1254
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Vault-Token");
1255
- if (req.method === "OPTIONS") {
1256
- res.writeHead(204);
1257
- res.end();
1258
- return;
1259
- }
1260
- try {
1261
- if (pathname.startsWith("/vault")) {
1262
- handleVaultRequest(req, res, pathname);
1263
- } else if (pathname.startsWith("/sso")) {
1264
- await handleSsoRequest(req, res, pathname);
1265
- } else if (pathname.startsWith("/iam")) {
1266
- await handleIamRequest(req, res, pathname);
1267
- } else if (pathname.startsWith("/workload")) {
1268
- await handleWorkloadRequest(req, res, pathname);
1269
- } else if (pathname === "/api/whoami") {
1270
- handleWhoamiRequest(req, res);
1271
- } else if (pathname === "/webhook") {
1272
- handleWebhook(req, res);
1273
- } else if (pathname === "/health" || pathname === "/") {
1274
- res.writeHead(200, { "Content-Type": "application/json" });
1275
- res.end(JSON.stringify({ status: "ok", service: "esv-mock-server" }));
1276
- } else {
1277
- res.writeHead(404, { "Content-Type": "application/json" });
1278
- res.end(JSON.stringify({ error: "Not found" }));
1279
- }
1280
- } catch (error) {
1281
- console.error("[ESV Server] Error handling request:", error);
1282
- res.writeHead(500, { "Content-Type": "application/json" });
1283
- res.end(JSON.stringify({ error: "Internal server error" }));
1284
- }
1285
- }
1286
- function startServer(options = {}) {
1287
- const { port = DEFAULT_PORT, host = DEFAULT_HOST, verbose = false } = options;
1288
- if (isStarted && server) {
1289
- if (verbose) {
1290
- console.log("[ESV Server] Server already running");
1291
- }
1292
- return Promise.resolve();
1293
- }
1294
- return new Promise((resolve, reject) => {
1295
- try {
1296
- initializeKeys();
1297
- resetState();
1298
- server = createServer((req, res) => {
1299
- handleRequest(req, res, verbose).catch((error) => {
1300
- console.error("[ESV Server] Unhandled error:", error);
1301
- if (!res.headersSent) {
1302
- res.writeHead(500, { "Content-Type": "application/json" });
1303
- res.end(JSON.stringify({ error: "Internal server error" }));
1304
- }
1305
- });
1306
- });
1307
- server.on("error", (error) => {
1308
- if (error.code === "EADDRINUSE") {
1309
- if (verbose) {
1310
- console.log(`[ESV Server] Port ${port} already in use, assuming server is running`);
1311
- }
1312
- isStarted = true;
1313
- resolve();
1314
- } else {
1315
- reject(error);
1316
- }
1317
- });
1318
- server.listen(port, host, () => {
1319
- isStarted = true;
1320
- console.log(`[ESV Server] Running at http://${host}:${port}/`);
1321
- console.log("[ESV Server] Endpoints:");
1322
- console.log(` - Vault: http://${host}:${port}/vault/v1/secret/data/esv/config`);
1323
- console.log(` - SSO: http://${host}:${port}/sso/*`);
1324
- console.log(` - IAM: http://${host}:${port}/iam/*`);
1325
- console.log(` - Workload: http://${host}:${port}/workload/*`);
1326
- console.log(` - Whoami: http://${host}:${port}/api/whoami`);
1327
- console.log(` - Webhook: http://${host}:${port}/webhook`);
1328
- resolve();
1329
- });
1330
- } catch (error) {
1331
- reject(error);
1332
- }
1333
- });
1334
- }
1335
- function stopServer() {
1336
- return new Promise((resolve, reject) => {
1337
- if (!server) {
1338
- isStarted = false;
1339
- resolve();
1340
- return;
1341
- }
1342
- server.close((error) => {
1343
- if (error) {
1344
- if (error.code === "ERR_SERVER_NOT_RUNNING") {
1345
- server = null;
1346
- isStarted = false;
1347
- resolve();
1348
- return;
1349
- }
1350
- reject(error);
1351
- return;
1352
- }
1353
- server = null;
1354
- isStarted = false;
1355
- console.log("[ESV Server] Stopped");
1356
- resolve();
1357
- });
1358
- });
1359
- }
1360
- function isServerRunning() {
1361
- return isStarted;
1362
- }
1363
- function getServerUrl(options = {}) {
1364
- const { port = DEFAULT_PORT, host = DEFAULT_HOST } = options;
1365
- return `http://${host}:${port}`;
1366
- }
1367
- export {
1368
- verifyJwt,
1369
- stopServer,
1370
- startServer,
1371
- signJwt,
1372
- setTestUser,
1373
- resetState,
1374
- isServerRunning,
1375
- getServerUrl,
1376
- getKeyId,
1377
- getJwks
1378
- };
1379
-
1380
- //# debugId=EDFA95029D6F1B9F64756E2164756E21
1
+ /**
2
+ * ESV Mock Server
3
+ *
4
+ * A zero-dependency mock server for testing Enterprise Standard integrations locally.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { startServer, stopServer } from '@enterprisestandard/esv/server';
9
+ * import { enterpriseStandard } from '@enterprisestandard/react';
10
+ *
11
+ * // In your test setup
12
+ * beforeAll(async () => {
13
+ * await startServer();
14
+ * });
15
+ *
16
+ * afterAll(async () => {
17
+ * await stopServer();
18
+ * });
19
+ *
20
+ * // Initialize with ES_VAULT_URL, ES_VAULT_TOKEN, and ES_VAULT_PATH set
21
+ * const es = await enterpriseStandard(undefined, {
22
+ * vaultUrl: process.env.ES_VAULT_URL,
23
+ * vaultToken: process.env.ES_VAULT_TOKEN,
24
+ * vaultPath: process.env.ES_VAULT_PATH,
25
+ * });
26
+ * ```
27
+ */
28
+ // Crypto utilities (for advanced testing scenarios)
29
+ export { getJwks, getKeyId, signJwt, verifyJwt } from './crypto.js';
30
+ // Main server functions
31
+ export { getServerUrl, isServerRunning, startServer, stopServer } from './server.js';
32
+ // State management (useful for test setup)
33
+ export { resetState, setTestUser } from './state.js';
34
+ //# sourceMappingURL=index.js.map