@executor-js/emulate 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +1044 -0
  2. package/dist/api.d.ts +24 -0
  3. package/dist/api.js +2665 -0
  4. package/dist/api.js.map +1 -0
  5. package/dist/chunk-D6EKRYGP.js +1615 -0
  6. package/dist/chunk-D6EKRYGP.js.map +1 -0
  7. package/dist/chunk-WVQMFHQM.js +83 -0
  8. package/dist/chunk-WVQMFHQM.js.map +1 -0
  9. package/dist/dist-7FDUSG5I.js +24368 -0
  10. package/dist/dist-7FDUSG5I.js.map +1 -0
  11. package/dist/dist-7N4COJHK.js +1814 -0
  12. package/dist/dist-7N4COJHK.js.map +1 -0
  13. package/dist/dist-BTEY33DJ.js +2334 -0
  14. package/dist/dist-BTEY33DJ.js.map +1 -0
  15. package/dist/dist-DK26ESP2.js +595 -0
  16. package/dist/dist-DK26ESP2.js.map +1 -0
  17. package/dist/dist-IYZPDKJW.js +1284 -0
  18. package/dist/dist-IYZPDKJW.js.map +1 -0
  19. package/dist/dist-JJ2ZRCAX.js +189 -0
  20. package/dist/dist-JJ2ZRCAX.js.map +1 -0
  21. package/dist/dist-K4CVTD6K.js +1570 -0
  22. package/dist/dist-K4CVTD6K.js.map +1 -0
  23. package/dist/dist-M3GVASMR.js +1254 -0
  24. package/dist/dist-M3GVASMR.js.map +1 -0
  25. package/dist/dist-OYYGWKZQ.js +1533 -0
  26. package/dist/dist-OYYGWKZQ.js.map +1 -0
  27. package/dist/dist-P3SBBRFR.js +3169 -0
  28. package/dist/dist-P3SBBRFR.js.map +1 -0
  29. package/dist/dist-RMPDKZUA.js +1183 -0
  30. package/dist/dist-RMPDKZUA.js.map +1 -0
  31. package/dist/dist-WBKONLOE.js +2154 -0
  32. package/dist/dist-WBKONLOE.js.map +1 -0
  33. package/dist/dist-XM5HSBDC.js +1090 -0
  34. package/dist/dist-XM5HSBDC.js.map +1 -0
  35. package/dist/dist-XVVIYXQG.js +4241 -0
  36. package/dist/dist-XVVIYXQG.js.map +1 -0
  37. package/dist/dist-YPRJYQHW.js +5109 -0
  38. package/dist/dist-YPRJYQHW.js.map +1 -0
  39. package/dist/dist-ZEC77OKZ.js +913 -0
  40. package/dist/dist-ZEC77OKZ.js.map +1 -0
  41. package/dist/fonts/GeistPixel-Square.woff2 +0 -0
  42. package/dist/fonts/favicon.ico +0 -0
  43. package/dist/fonts/geist-sans.woff2 +0 -0
  44. package/dist/helpers-LXLP3DFE-LBOTATT5.js +17 -0
  45. package/dist/helpers-LXLP3DFE-LBOTATT5.js.map +1 -0
  46. package/dist/index.js +3005 -0
  47. package/dist/index.js.map +1 -0
  48. package/package.json +83 -0
@@ -0,0 +1,1254 @@
1
+ import {
2
+ SignJWT,
3
+ exportJWK,
4
+ generateKeyPair
5
+ } from "./chunk-D6EKRYGP.js";
6
+
7
+ // ../@emulators/microsoft/dist/index.js
8
+ import { randomUUID } from "crypto";
9
+ import { createHash, randomBytes } from "crypto";
10
+ import { timingSafeEqual } from "crypto";
11
+ function getMicrosoftStore(store) {
12
+ return {
13
+ users: store.collection("microsoft.users", ["oid", "email"]),
14
+ oauthClients: store.collection("microsoft.oauth_clients", ["client_id"])
15
+ };
16
+ }
17
+ var DEFAULT_TENANT_ID = "9188040d-6c67-4c5b-b112-36a304b66dad";
18
+ function generateOid() {
19
+ return randomUUID();
20
+ }
21
+ function createErrorHandler(documentationUrl) {
22
+ return async (c, next) => {
23
+ if (documentationUrl) {
24
+ c.set("docsUrl", documentationUrl);
25
+ }
26
+ await next();
27
+ };
28
+ }
29
+ var errorHandler = createErrorHandler();
30
+ var isDebug = typeof process !== "undefined" && (process.env.DEBUG === "1" || process.env.DEBUG === "true" || process.env.EMULATE_DEBUG === "1");
31
+ function debug(label, ...args) {
32
+ if (isDebug) {
33
+ console.log(`[${label}]`, ...args);
34
+ }
35
+ }
36
+ function escapeHtml(s) {
37
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
38
+ }
39
+ function escapeAttr(s) {
40
+ return escapeHtml(s).replace(/'/g, "&#39;");
41
+ }
42
+ var CSS = `
43
+ @font-face{
44
+ font-family:'Geist';font-style:normal;font-weight:100 900;font-display:swap;
45
+ src:url('/_emulate/fonts/geist-sans.woff2') format('woff2');
46
+ }
47
+ @font-face{
48
+ font-family:'Geist Pixel';font-style:normal;font-weight:400;font-display:swap;
49
+ src:url('/_emulate/fonts/GeistPixel-Square.woff2') format('woff2');
50
+ }
51
+ *{box-sizing:border-box;margin:0;padding:0}
52
+ body{
53
+ font-family:'Geist',-apple-system,BlinkMacSystemFont,sans-serif;
54
+ background:#000;color:#33ff00;min-height:100vh;
55
+ -webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
56
+ }
57
+ .emu-bar{
58
+ border-bottom:1px solid #0a3300;padding:10px 20px;
59
+ display:flex;align-items:center;gap:10px;font-size:.8125rem;color:#1a8c00;
60
+ }
61
+ .emu-bar-title{font-weight:600;color:#33ff00;font-family:'Geist Pixel',monospace;}
62
+ .emu-bar-links{margin-left:auto;display:flex;gap:16px;}
63
+ .emu-bar-links a{
64
+ color:#1a8c00;font-size:.75rem;text-decoration:none;transition:color .15s;
65
+ }
66
+ .emu-bar-links a:hover{color:#33ff00;}
67
+ .emu-bar-links a .full{display:inline;}
68
+ .emu-bar-links a .short{display:none;}
69
+ @media(max-width:600px){
70
+ .emu-bar-links a .full{display:none;}
71
+ .emu-bar-links a .short{display:inline;}
72
+ }
73
+
74
+ .content{
75
+ display:flex;align-items:center;justify-content:center;
76
+ min-height:calc(100vh - 42px);padding:24px 16px;
77
+ }
78
+ .content-inner{width:100%;max-width:420px;}
79
+ .card-title{
80
+ font-family:'Geist Pixel',monospace;
81
+ font-size:1.125rem;font-weight:600;margin-bottom:4px;color:#33ff00;
82
+ }
83
+ .card-subtitle{color:#1a8c00;font-size:.8125rem;margin-bottom:18px;line-height:1.45;}
84
+ .powered-by{
85
+ position:fixed;bottom:0;left:0;right:0;
86
+ text-align:center;padding:12px;font-size:.6875rem;color:#0a3300;
87
+ font-family:'Geist Pixel',monospace;
88
+ }
89
+ .powered-by a{color:#1a8c00;text-decoration:none;transition:color .15s;}
90
+ .powered-by a:hover{color:#33ff00;}
91
+
92
+ .error-title{
93
+ font-family:'Geist Pixel',monospace;
94
+ color:#ff4444;font-size:1.125rem;font-weight:600;margin-bottom:8px;
95
+ }
96
+ .error-msg{color:#1a8c00;font-size:.875rem;line-height:1.5;}
97
+ .error-card{text-align:center;}
98
+
99
+ .user-form{margin-bottom:8px;}
100
+ .user-form:last-of-type{margin-bottom:0;}
101
+ .user-btn{
102
+ width:100%;display:flex;align-items:center;gap:12px;
103
+ padding:10px 12px;border:1px solid #0a3300;border-radius:8px;
104
+ background:#000;color:inherit;cursor:pointer;text-align:left;
105
+ font:inherit;transition:border-color .15s;
106
+ }
107
+ .user-btn:hover{border-color:#33ff00;}
108
+ .avatar{
109
+ width:36px;height:36px;border-radius:50%;
110
+ background:#0a3300;color:#33ff00;font-weight:600;font-size:.875rem;
111
+ display:flex;align-items:center;justify-content:center;flex-shrink:0;
112
+ font-family:'Geist Pixel',monospace;
113
+ }
114
+ .user-text{min-width:0;}
115
+ .user-login{font-weight:600;font-size:.875rem;display:block;color:#33ff00;}
116
+ .user-meta{color:#1a8c00;font-size:.75rem;margin-top:1px;}
117
+ .user-email{font-size:.6875rem;color:#116600;word-break:break-all;margin-top:1px;}
118
+
119
+ .settings-layout{
120
+ max-width:920px;margin:0 auto;padding:28px 20px;
121
+ display:flex;gap:28px;
122
+ }
123
+ .settings-sidebar{width:200px;flex-shrink:0;}
124
+ .settings-sidebar a{
125
+ display:block;padding:6px 10px;border-radius:6px;color:#1a8c00;
126
+ text-decoration:none;font-size:.8125rem;transition:color .15s;
127
+ }
128
+ .settings-sidebar a:hover{color:#33ff00;}
129
+ .settings-sidebar a.active{color:#33ff00;font-weight:600;}
130
+ .settings-main{flex:1;min-width:0;}
131
+
132
+ .s-card{
133
+ padding:18px 0;margin-bottom:14px;border-bottom:1px solid #0a3300;
134
+ }
135
+ .s-card:last-child{border-bottom:none;}
136
+ .s-card-header{display:flex;align-items:center;gap:14px;margin-bottom:14px;}
137
+ .s-icon{
138
+ width:42px;height:42px;border-radius:8px;
139
+ background:#0a3300;display:flex;align-items:center;justify-content:center;
140
+ font-size:1.125rem;font-weight:700;color:#116600;flex-shrink:0;
141
+ font-family:'Geist Pixel',monospace;
142
+ }
143
+ .s-title{
144
+ font-family:'Geist Pixel',monospace;
145
+ font-size:1.25rem;font-weight:600;color:#33ff00;
146
+ }
147
+ .s-subtitle{font-size:.75rem;color:#1a8c00;margin-top:2px;}
148
+ .section-heading{
149
+ font-size:.9375rem;font-weight:600;margin-bottom:10px;color:#33ff00;
150
+ display:flex;align-items:center;justify-content:space-between;
151
+ }
152
+ .perm-list{list-style:none;}
153
+ .perm-list li{padding:5px 0;font-size:.8125rem;display:flex;align-items:center;gap:6px;color:#1a8c00;}
154
+ .check{color:#33ff00;}
155
+ .org-row{
156
+ display:flex;align-items:center;gap:8px;padding:7px 0;
157
+ border-bottom:1px solid #0a3300;font-size:.8125rem;
158
+ }
159
+ .org-row:last-child{border-bottom:none;}
160
+ .org-icon{
161
+ width:22px;height:22px;border-radius:4px;background:#0a3300;
162
+ display:flex;align-items:center;justify-content:center;
163
+ font-size:.625rem;font-weight:700;color:#116600;flex-shrink:0;
164
+ font-family:'Geist Pixel',monospace;
165
+ }
166
+ .org-name{font-weight:600;color:#33ff00;}
167
+ .badge{font-size:.6875rem;padding:1px 7px;border-radius:999px;font-weight:500;}
168
+ .badge-granted{background:#0a3300;color:#33ff00;}
169
+ .badge-denied{background:#1a0a0a;color:#ff4444;}
170
+ .badge-requested{background:#0a3300;color:#1a8c00;}
171
+ .btn-revoke{
172
+ display:inline-block;padding:5px 14px;border-radius:6px;
173
+ border:1px solid #0a3300;background:transparent;color:#ff4444;
174
+ font-size:.75rem;font-weight:600;cursor:pointer;transition:border-color .15s;
175
+ }
176
+ .btn-revoke:hover{border-color:#ff4444;}
177
+ .info-text{color:#1a8c00;font-size:.75rem;line-height:1.5;margin-top:10px;}
178
+ .info-text a,.section-heading a{color:#1a8c00;text-decoration:none;transition:color .15s;}
179
+ .info-text a:hover,.section-heading a:hover{color:#33ff00;}
180
+ code{font-family:'Geist Mono','SF Mono',ui-monospace,monospace;font-size:.8125rem;color:#33ff00;word-break:break-all;}
181
+ .code-block{
182
+ background:#020;border:1px solid #0a3300;border-radius:6px;padding:10px 12px;
183
+ margin:8px 0 12px;overflow-x:auto;
184
+ }
185
+ .code-block code{white-space:pre;word-break:normal;display:block;line-height:1.5;}
186
+ .app-link{
187
+ display:flex;align-items:center;gap:12px;padding:12px;
188
+ border:1px solid #0a3300;border-radius:8px;background:#000;
189
+ text-decoration:none;color:inherit;margin-bottom:8px;transition:border-color .15s;
190
+ }
191
+ .app-link:hover{border-color:#33ff00;}
192
+ .app-link-name{font-weight:600;font-size:.875rem;color:#33ff00;}
193
+ .app-link-scopes{font-size:.6875rem;color:#1a8c00;margin-top:1px;}
194
+ .empty{color:#1a8c00;text-align:center;padding:28px 0;font-size:.875rem;}
195
+
196
+ .inspector-layout{max-width:960px;margin:0 auto;padding:28px 20px;}
197
+ .inspector-tabs{display:flex;gap:4px;margin-bottom:20px;}
198
+ .inspector-tabs a{
199
+ padding:7px 16px;border-radius:6px;text-decoration:none;
200
+ font-size:.8125rem;color:#1a8c00;border:1px solid transparent;
201
+ transition:color .15s,border-color .15s;
202
+ }
203
+ .inspector-tabs a:hover{color:#33ff00;}
204
+ .inspector-tabs a.active{color:#33ff00;font-weight:600;border-color:#0a3300;background:#0a3300;}
205
+ .inspector-section{margin-bottom:24px;}
206
+ .inspector-section h2{
207
+ font-family:'Geist Pixel',monospace;
208
+ font-size:1rem;font-weight:600;color:#33ff00;margin-bottom:10px;
209
+ }
210
+ .inspector-section h3{
211
+ font-family:'Geist Pixel',monospace;
212
+ font-size:.875rem;font-weight:600;color:#1a8c00;margin:16px 0 8px;
213
+ }
214
+ .inspector-table{width:100%;border-collapse:collapse;margin-bottom:12px;}
215
+ .inspector-table th,.inspector-table td{
216
+ text-align:left;padding:8px 12px;border-bottom:1px solid #0a3300;
217
+ font-size:.8125rem;
218
+ }
219
+ .inspector-table th{color:#1a8c00;font-weight:600;font-size:.75rem;text-transform:uppercase;letter-spacing:.04em;}
220
+ .inspector-table td{color:#33ff00;}
221
+ .inspector-table tbody tr{transition:background .1s;}
222
+ .inspector-table tbody tr:hover{background:#0a3300;}
223
+ .inspector-empty{color:#1a8c00;text-align:center;padding:20px 0;font-size:.8125rem;}
224
+
225
+ .checkout-layout{
226
+ display:flex;min-height:calc(100vh - 42px);
227
+ }
228
+ .checkout-summary{
229
+ flex:1;background:#020;padding:48px 40px 48px 10%;
230
+ display:flex;flex-direction:column;justify-content:center;
231
+ border-right:1px solid #0a3300;
232
+ }
233
+ .checkout-form-side{
234
+ flex:1;background:#000;padding:48px 10% 48px 40px;
235
+ display:flex;flex-direction:column;justify-content:center;
236
+ }
237
+ .checkout-merchant{
238
+ display:flex;align-items:center;gap:10px;margin-bottom:6px;
239
+ }
240
+ .checkout-merchant-name{
241
+ font-family:'Geist Pixel',monospace;
242
+ font-size:.9375rem;font-weight:600;color:#33ff00;
243
+ }
244
+ .checkout-test-badge{
245
+ font-size:.625rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;
246
+ background:#0a3300;color:#1a8c00;padding:2px 8px;border-radius:4px;
247
+ }
248
+ .checkout-total{
249
+ font-family:'Geist Pixel',monospace;
250
+ font-size:2rem;font-weight:700;color:#33ff00;margin:8px 0 28px;
251
+ }
252
+ .checkout-line-item{
253
+ display:flex;align-items:center;gap:14px;padding:14px 0;
254
+ border-bottom:1px solid #0a3300;
255
+ }
256
+ .checkout-line-item:first-child{border-top:1px solid #0a3300;}
257
+ .checkout-item-icon{
258
+ width:42px;height:42px;border-radius:6px;background:#0a3300;
259
+ display:flex;align-items:center;justify-content:center;flex-shrink:0;
260
+ font-family:'Geist Pixel',monospace;font-size:.875rem;font-weight:700;color:#116600;
261
+ }
262
+ .checkout-item-details{flex:1;min-width:0;}
263
+ .checkout-item-name{font-size:.875rem;font-weight:600;color:#33ff00;}
264
+ .checkout-item-qty{font-size:.75rem;color:#1a8c00;margin-top:2px;}
265
+ .checkout-item-price{
266
+ font-size:.875rem;font-weight:600;color:#33ff00;text-align:right;white-space:nowrap;
267
+ }
268
+ .checkout-item-unit{font-size:.6875rem;color:#1a8c00;text-align:right;margin-top:2px;}
269
+ .checkout-totals{margin-top:20px;}
270
+ .checkout-totals-row{
271
+ display:flex;justify-content:space-between;padding:6px 0;
272
+ font-size:.8125rem;color:#1a8c00;
273
+ }
274
+ .checkout-totals-row.total{
275
+ border-top:1px solid #0a3300;margin-top:8px;padding-top:14px;
276
+ font-size:.9375rem;font-weight:600;color:#33ff00;
277
+ }
278
+ .checkout-form-section{margin-bottom:24px;}
279
+ .checkout-form-label{
280
+ font-size:.8125rem;font-weight:600;color:#33ff00;margin-bottom:8px;display:block;
281
+ }
282
+ .checkout-input{
283
+ width:100%;padding:10px 12px;border:1px solid #0a3300;border-radius:6px;
284
+ background:#020;color:#33ff00;font:inherit;font-size:.875rem;
285
+ transition:border-color .15s;outline:none;
286
+ }
287
+ .checkout-input:focus{border-color:#33ff00;}
288
+ .checkout-input::placeholder{color:#116600;}
289
+ .checkout-card-box{
290
+ border:1px solid #0a3300;border-radius:6px;padding:14px;
291
+ background:#020;
292
+ }
293
+ .checkout-card-row{
294
+ display:flex;gap:12px;margin-top:10px;
295
+ }
296
+ .checkout-card-row .checkout-input{flex:1;}
297
+ .checkout-sim-note{
298
+ font-size:.6875rem;color:#1a8c00;margin-top:10px;text-align:center;
299
+ font-style:italic;
300
+ }
301
+ .checkout-pay-btn{
302
+ width:100%;padding:14px;border:none;border-radius:8px;
303
+ background:#33ff00;color:#000;font:inherit;font-size:.9375rem;font-weight:700;
304
+ cursor:pointer;transition:background .15s;
305
+ font-family:'Geist Pixel',monospace;
306
+ }
307
+ .checkout-pay-btn:hover{background:#44ff22;}
308
+ .checkout-cancel{
309
+ text-align:center;margin-top:14px;
310
+ }
311
+ .checkout-cancel a{
312
+ color:#1a8c00;text-decoration:none;font-size:.8125rem;
313
+ transition:color .15s;
314
+ }
315
+ .checkout-cancel a:hover{color:#33ff00;}
316
+ @media(max-width:768px){
317
+ .checkout-layout{flex-direction:column;}
318
+ .checkout-summary{padding:32px 20px;border-right:none;border-bottom:1px solid #0a3300;}
319
+ .checkout-form-side{padding:32px 20px;}
320
+ }
321
+ `;
322
+ var POWERED_BY = `<div class="powered-by">Powered by <a href="https://emulate.dev" target="_blank" rel="noopener">emulate</a></div>`;
323
+ function emuBar(service) {
324
+ const title = service ? `${escapeHtml(service)} Emulator` : "Emulator";
325
+ return `<div class="emu-bar">
326
+ <span class="emu-bar-title">${title}</span>
327
+ <nav class="emu-bar-links">
328
+ <a href="https://github.com/vercel-labs/emulate/issues" target="_blank" rel="noopener"><span class="full">Report Issue</span><span class="short">Report</span></a>
329
+ <a href="https://github.com/vercel-labs/emulate" target="_blank" rel="noopener"><span class="full">Source Code</span><span class="short">Source</span></a>
330
+ <a href="https://emulate.dev" target="_blank" rel="noopener"><span class="full">Learn More</span><span class="short">Learn</span></a>
331
+ </nav>
332
+ </div>`;
333
+ }
334
+ function head(title) {
335
+ return `<!DOCTYPE html>
336
+ <html lang="en">
337
+ <head>
338
+ <meta charset="utf-8"/>
339
+ <meta name="viewport" content="width=device-width,initial-scale=1"/>
340
+ <link rel="icon" href="/_emulate/favicon.ico"/>
341
+ <title>${escapeHtml(title)} | emulate</title>
342
+ <style>${CSS}</style>
343
+ </head>`;
344
+ }
345
+ function renderCardPage(title, subtitle, body, service) {
346
+ return `${head(title)}
347
+ <body>
348
+ ${emuBar(service)}
349
+ <div class="content">
350
+ <div class="content-inner">
351
+ <div class="card-title">${escapeHtml(title)}</div>
352
+ <div class="card-subtitle">${subtitle}</div>
353
+ ${body}
354
+ </div>
355
+ </div>
356
+ ${POWERED_BY}
357
+ </body></html>`;
358
+ }
359
+ function renderErrorPage(title, message, service) {
360
+ return `${head(title)}
361
+ <body>
362
+ ${emuBar(service)}
363
+ <div class="content">
364
+ <div class="content-inner error-card">
365
+ <div class="error-title">${escapeHtml(title)}</div>
366
+ <div class="error-msg">${escapeHtml(message)}</div>
367
+ </div>
368
+ </div>
369
+ ${POWERED_BY}
370
+ </body></html>`;
371
+ }
372
+ function renderFormPostPage(action, fields, service) {
373
+ const hiddens = Object.entries(fields).filter(([, v]) => v != null).map(([k, v]) => `<input type="hidden" name="${escapeAttr(k)}" value="${escapeAttr(v)}"/>`).join("\n");
374
+ return `${head("Redirecting")}
375
+ <body onload="document.forms[0].submit()">
376
+ ${emuBar(service)}
377
+ <div class="content">
378
+ <div class="content-inner" style="text-align:center">
379
+ <div class="card-subtitle">Redirecting&hellip;</div>
380
+ <form method="POST" action="${escapeAttr(action)}">
381
+ ${hiddens}
382
+ <noscript><button type="submit" class="user-btn" style="margin-top:12px;justify-content:center">
383
+ <span class="user-login">Continue</span>
384
+ </button></noscript>
385
+ </form>
386
+ </div>
387
+ </div>
388
+ ${POWERED_BY}
389
+ </body></html>`;
390
+ }
391
+ function renderUserButton(opts) {
392
+ const hiddens = Object.entries(opts.hiddenFields).map(([k, v]) => `<input type="hidden" name="${escapeAttr(k)}" value="${escapeAttr(v)}"/>`).join("");
393
+ const nameLine = opts.name ? `<div class="user-meta">${escapeHtml(opts.name)}</div>` : "";
394
+ const emailLine = opts.email ? `<div class="user-email">${escapeHtml(opts.email)}</div>` : "";
395
+ return `<form class="user-form" method="post" action="${escapeAttr(opts.formAction)}">
396
+ ${hiddens}
397
+ <button type="submit" class="user-btn">
398
+ <span class="avatar">${escapeHtml(opts.letter)}</span>
399
+ <span class="user-text">
400
+ <span class="user-login">${escapeHtml(opts.login)}</span>
401
+ ${nameLine}${emailLine}
402
+ </span>
403
+ </button>
404
+ </form>`;
405
+ }
406
+ function normalizeUri(uri) {
407
+ try {
408
+ const u = new URL(uri);
409
+ return `${u.origin}${u.pathname.replace(/\/+$/, "")}`;
410
+ } catch {
411
+ return uri.replace(/\/+$/, "").split("?")[0];
412
+ }
413
+ }
414
+ function matchesRedirectUri(incoming, registered) {
415
+ const normalized = normalizeUri(incoming);
416
+ return registered.some((r) => normalizeUri(r) === normalized);
417
+ }
418
+ function constantTimeSecretEqual(a, b) {
419
+ const bufA = Buffer.from(a, "utf-8");
420
+ const bufB = Buffer.from(b, "utf-8");
421
+ if (bufA.length !== bufB.length) return false;
422
+ return timingSafeEqual(bufA, bufB);
423
+ }
424
+ function bodyStr(v) {
425
+ if (typeof v === "string") return v;
426
+ if (Array.isArray(v) && typeof v[0] === "string") return v[0];
427
+ return "";
428
+ }
429
+ var keyPairPromise = generateKeyPair("RS256");
430
+ var KID = "emulate-microsoft-1";
431
+ var PENDING_CODE_TTL_MS = 10 * 60 * 1e3;
432
+ function getPendingCodes(store) {
433
+ let map = store.getData("microsoft.oauth.pendingCodes");
434
+ if (!map) {
435
+ map = /* @__PURE__ */ new Map();
436
+ store.setData("microsoft.oauth.pendingCodes", map);
437
+ }
438
+ return map;
439
+ }
440
+ function getRefreshTokens(store) {
441
+ let map = store.getData("microsoft.oauth.refreshTokens");
442
+ if (!map) {
443
+ map = /* @__PURE__ */ new Map();
444
+ store.setData("microsoft.oauth.refreshTokens", map);
445
+ }
446
+ return map;
447
+ }
448
+ function isPendingCodeExpired(p) {
449
+ return Date.now() - p.created_at > PENDING_CODE_TTL_MS;
450
+ }
451
+ var SERVICE_LABEL = "Microsoft";
452
+ async function createIdToken(user, clientId, nonce, baseUrl) {
453
+ const { privateKey } = await keyPairPromise;
454
+ const now = Math.floor(Date.now() / 1e3);
455
+ const builder = new SignJWT({
456
+ sub: user.oid,
457
+ email: user.email,
458
+ name: user.name,
459
+ given_name: user.given_name,
460
+ family_name: user.family_name,
461
+ preferred_username: user.preferred_username,
462
+ oid: user.oid,
463
+ tid: user.tenant_id,
464
+ ver: "2.0",
465
+ ...nonce ? { nonce } : {}
466
+ }).setProtectedHeader({ alg: "RS256", kid: KID, typ: "JWT" }).setIssuer(`${baseUrl}/${user.tenant_id}/v2.0`).setAudience(clientId).setIssuedAt(now).setExpirationTime("1h");
467
+ return builder.sign(privateKey);
468
+ }
469
+ function oauthRoutes({ app, store, baseUrl, tokenMap }) {
470
+ const ms = getMicrosoftStore(store);
471
+ const oidcConfig = (tenantId) => ({
472
+ issuer: `${baseUrl}/${tenantId}/v2.0`,
473
+ authorization_endpoint: `${baseUrl}/oauth2/v2.0/authorize`,
474
+ token_endpoint: `${baseUrl}/oauth2/v2.0/token`,
475
+ userinfo_endpoint: `${baseUrl}/oidc/userinfo`,
476
+ end_session_endpoint: `${baseUrl}/oauth2/v2.0/logout`,
477
+ jwks_uri: `${baseUrl}/discovery/v2.0/keys`,
478
+ response_types_supported: ["code"],
479
+ response_modes_supported: ["query", "fragment", "form_post"],
480
+ subject_types_supported: ["pairwise"],
481
+ id_token_signing_alg_values_supported: ["RS256"],
482
+ scopes_supported: ["openid", "email", "profile", "offline_access", "User.Read", ".default"],
483
+ grant_types_supported: ["authorization_code", "refresh_token", "client_credentials"],
484
+ token_endpoint_auth_methods_supported: ["client_secret_post", "client_secret_basic"],
485
+ claims_supported: [
486
+ "sub",
487
+ "iss",
488
+ "aud",
489
+ "exp",
490
+ "iat",
491
+ "nonce",
492
+ "name",
493
+ "email",
494
+ "given_name",
495
+ "family_name",
496
+ "preferred_username",
497
+ "oid",
498
+ "tid",
499
+ "ver"
500
+ ],
501
+ code_challenge_methods_supported: ["plain", "S256"]
502
+ });
503
+ app.get("/.well-known/openid-configuration", (c) => {
504
+ return c.json(oidcConfig(DEFAULT_TENANT_ID));
505
+ });
506
+ app.get("/:tenant/v2.0/.well-known/openid-configuration", (c) => {
507
+ const tenant = c.req.param("tenant");
508
+ return c.json(
509
+ oidcConfig(
510
+ tenant === "common" || tenant === "organizations" || tenant === "consumers" ? DEFAULT_TENANT_ID : tenant
511
+ )
512
+ );
513
+ });
514
+ app.get("/discovery/v2.0/keys", async (c) => {
515
+ const { publicKey } = await keyPairPromise;
516
+ const jwk = await exportJWK(publicKey);
517
+ return c.json({
518
+ keys: [
519
+ {
520
+ ...jwk,
521
+ kid: KID,
522
+ use: "sig",
523
+ alg: "RS256"
524
+ }
525
+ ]
526
+ });
527
+ });
528
+ app.get("/oauth2/v2.0/authorize", (c) => {
529
+ const client_id = c.req.query("client_id") ?? "";
530
+ const redirect_uri = c.req.query("redirect_uri") ?? "";
531
+ const scope = c.req.query("scope") ?? "";
532
+ const state = c.req.query("state") ?? "";
533
+ const nonce = c.req.query("nonce") ?? "";
534
+ const response_mode = c.req.query("response_mode") ?? "query";
535
+ const code_challenge = c.req.query("code_challenge") ?? "";
536
+ const code_challenge_method = c.req.query("code_challenge_method") ?? "";
537
+ const clientsConfigured = ms.oauthClients.all().length > 0;
538
+ let clientName = "";
539
+ if (clientsConfigured) {
540
+ const client = ms.oauthClients.findOneBy("client_id", client_id);
541
+ if (!client) {
542
+ return c.html(
543
+ renderErrorPage("Application not found", `The client_id '${client_id}' is not registered.`, SERVICE_LABEL),
544
+ 400
545
+ );
546
+ }
547
+ if (redirect_uri && !matchesRedirectUri(redirect_uri, client.redirect_uris)) {
548
+ return c.html(
549
+ renderErrorPage(
550
+ "Redirect URI mismatch",
551
+ "The redirect_uri is not registered for this application.",
552
+ SERVICE_LABEL
553
+ ),
554
+ 400
555
+ );
556
+ }
557
+ clientName = client.name;
558
+ }
559
+ const subtitleText = clientName ? `Sign in to <strong>${escapeHtml(clientName)}</strong> with your Microsoft account.` : "Choose a seeded user to continue.";
560
+ const users = ms.users.all();
561
+ const userButtons = users.map((user) => {
562
+ return renderUserButton({
563
+ letter: (user.email[0] ?? "?").toUpperCase(),
564
+ login: user.email,
565
+ name: user.name,
566
+ email: user.email,
567
+ formAction: `${baseUrl}/oauth2/v2.0/authorize/callback`,
568
+ hiddenFields: {
569
+ email: user.email,
570
+ redirect_uri,
571
+ scope,
572
+ state,
573
+ nonce,
574
+ client_id,
575
+ response_mode,
576
+ code_challenge,
577
+ code_challenge_method
578
+ }
579
+ });
580
+ }).join("\n");
581
+ const body = users.length === 0 ? '<p class="empty">No users in the emulator store.</p>' : userButtons;
582
+ return c.html(renderCardPage("Sign in with Microsoft", subtitleText, body, SERVICE_LABEL));
583
+ });
584
+ app.post("/oauth2/v2.0/authorize/callback", async (c) => {
585
+ const body = await c.req.parseBody();
586
+ const email = bodyStr(body.email);
587
+ const redirect_uri = bodyStr(body.redirect_uri);
588
+ const scope = bodyStr(body.scope);
589
+ const state = bodyStr(body.state);
590
+ const client_id = bodyStr(body.client_id);
591
+ const nonce = bodyStr(body.nonce);
592
+ const response_mode = bodyStr(body.response_mode) || "query";
593
+ const code_challenge = bodyStr(body.code_challenge);
594
+ const code_challenge_method = bodyStr(body.code_challenge_method);
595
+ const clientsConfigured = ms.oauthClients.all().length > 0;
596
+ if (clientsConfigured) {
597
+ const client = ms.oauthClients.findOneBy("client_id", client_id);
598
+ if (!client) {
599
+ return c.html(
600
+ renderErrorPage("Application not found", `The client_id '${client_id}' is not registered.`, SERVICE_LABEL),
601
+ 400
602
+ );
603
+ }
604
+ if (redirect_uri && !matchesRedirectUri(redirect_uri, client.redirect_uris)) {
605
+ return c.html(
606
+ renderErrorPage(
607
+ "Redirect URI mismatch",
608
+ "The redirect_uri is not registered for this application.",
609
+ SERVICE_LABEL
610
+ ),
611
+ 400
612
+ );
613
+ }
614
+ }
615
+ const code = randomBytes(20).toString("hex");
616
+ getPendingCodes(store).set(code, {
617
+ email,
618
+ scope,
619
+ redirectUri: redirect_uri,
620
+ clientId: client_id,
621
+ nonce: nonce || null,
622
+ codeChallenge: code_challenge || null,
623
+ codeChallengeMethod: code_challenge_method || null,
624
+ created_at: Date.now()
625
+ });
626
+ debug("microsoft.oauth", `[Microsoft callback] code=${code.slice(0, 8)}... email=${email}`);
627
+ if (response_mode === "form_post") {
628
+ return c.html(renderFormPostPage(redirect_uri, { code, state }, SERVICE_LABEL));
629
+ }
630
+ const url = new URL(redirect_uri);
631
+ url.searchParams.set("code", code);
632
+ if (state) url.searchParams.set("state", state);
633
+ return c.redirect(url.toString(), 302);
634
+ });
635
+ app.post("/oauth2/v2.0/token", async (c) => {
636
+ const contentType = c.req.header("Content-Type") ?? "";
637
+ const rawText = await c.req.text();
638
+ let body;
639
+ if (contentType.includes("application/json")) {
640
+ try {
641
+ body = JSON.parse(rawText);
642
+ } catch {
643
+ body = {};
644
+ }
645
+ } else {
646
+ body = Object.fromEntries(new URLSearchParams(rawText));
647
+ }
648
+ const grant_type = typeof body.grant_type === "string" ? body.grant_type : "";
649
+ const code = typeof body.code === "string" ? body.code : "";
650
+ let client_id = typeof body.client_id === "string" ? body.client_id : "";
651
+ let client_secret = typeof body.client_secret === "string" ? body.client_secret : "";
652
+ const refresh_token = typeof body.refresh_token === "string" ? body.refresh_token : "";
653
+ const redirect_uri = typeof body.redirect_uri === "string" ? body.redirect_uri : "";
654
+ const code_verifier = typeof body.code_verifier === "string" ? body.code_verifier : void 0;
655
+ const scope = typeof body.scope === "string" ? body.scope : "";
656
+ const authHeader = c.req.header("Authorization") ?? "";
657
+ if (authHeader.startsWith("Basic ")) {
658
+ const decoded = Buffer.from(authHeader.slice(6), "base64").toString();
659
+ const sep = decoded.indexOf(":");
660
+ if (sep !== -1) {
661
+ const headerId = decodeURIComponent(decoded.slice(0, sep));
662
+ const headerSecret = decodeURIComponent(decoded.slice(sep + 1));
663
+ if (!client_id) client_id = headerId;
664
+ if (!client_secret) client_secret = headerSecret;
665
+ }
666
+ }
667
+ if (grant_type === "authorization_code") {
668
+ const clientsConfigured = ms.oauthClients.all().length > 0;
669
+ if (clientsConfigured) {
670
+ const client = ms.oauthClients.findOneBy("client_id", client_id);
671
+ if (!client) {
672
+ return c.json({ error: "invalid_client", error_description: "The client_id is incorrect." }, 401);
673
+ }
674
+ if (!constantTimeSecretEqual(client_secret, client.client_secret)) {
675
+ return c.json({ error: "invalid_client", error_description: "The client_secret is incorrect." }, 401);
676
+ }
677
+ }
678
+ const pendingMap = getPendingCodes(store);
679
+ const pending = pendingMap.get(code);
680
+ if (!pending) {
681
+ return c.json({ error: "invalid_grant", error_description: "The code is incorrect or expired." }, 400);
682
+ }
683
+ if (isPendingCodeExpired(pending)) {
684
+ pendingMap.delete(code);
685
+ return c.json({ error: "invalid_grant", error_description: "The code is incorrect or expired." }, 400);
686
+ }
687
+ if (pending.redirectUri && redirect_uri && pending.redirectUri !== redirect_uri) {
688
+ pendingMap.delete(code);
689
+ return c.json(
690
+ {
691
+ error: "invalid_grant",
692
+ error_description: "The redirect_uri does not match the one used in the authorization request."
693
+ },
694
+ 400
695
+ );
696
+ }
697
+ if (pending.codeChallenge !== null) {
698
+ if (code_verifier === void 0) {
699
+ return c.json({ error: "invalid_grant", error_description: "PKCE verification failed." }, 400);
700
+ }
701
+ const method = (pending.codeChallengeMethod ?? "plain").toLowerCase();
702
+ if (method === "s256") {
703
+ const expected = createHash("sha256").update(code_verifier).digest("base64url");
704
+ if (expected !== pending.codeChallenge) {
705
+ return c.json({ error: "invalid_grant", error_description: "PKCE verification failed." }, 400);
706
+ }
707
+ } else if (method === "plain") {
708
+ if (code_verifier !== pending.codeChallenge) {
709
+ return c.json({ error: "invalid_grant", error_description: "PKCE verification failed." }, 400);
710
+ }
711
+ } else {
712
+ return c.json({ error: "invalid_grant", error_description: "PKCE verification failed." }, 400);
713
+ }
714
+ }
715
+ pendingMap.delete(code);
716
+ const user = ms.users.findOneBy("email", pending.email);
717
+ if (!user) {
718
+ return c.json({ error: "invalid_grant", error_description: "User not found." }, 400);
719
+ }
720
+ const accessToken = "microsoft_" + randomBytes(20).toString("base64url");
721
+ const refreshToken = "r_microsoft_" + randomBytes(20).toString("base64url");
722
+ const scopes = pending.scope ? pending.scope.split(/\s+/).filter(Boolean) : [];
723
+ if (tokenMap) {
724
+ tokenMap.set(accessToken, { login: user.email, id: user.id, scopes });
725
+ }
726
+ getRefreshTokens(store).set(refreshToken, {
727
+ email: user.email,
728
+ clientId: pending.clientId,
729
+ scope: pending.scope,
730
+ nonce: pending.nonce
731
+ });
732
+ const idToken = await createIdToken(user, pending.clientId, pending.nonce, baseUrl);
733
+ debug("microsoft.oauth", `[Microsoft token] issued token for ${user.email}`);
734
+ return c.json({
735
+ access_token: accessToken,
736
+ token_type: "Bearer",
737
+ expires_in: 3600,
738
+ scope: pending.scope || "openid email profile",
739
+ refresh_token: refreshToken,
740
+ id_token: idToken
741
+ });
742
+ }
743
+ if (grant_type === "refresh_token") {
744
+ const refreshMap = getRefreshTokens(store);
745
+ const stored = refreshMap.get(refresh_token);
746
+ if (!stored) {
747
+ return c.json({ error: "invalid_grant", error_description: "The refresh_token is invalid." }, 400);
748
+ }
749
+ const user = ms.users.findOneBy("email", stored.email);
750
+ if (!user) {
751
+ return c.json({ error: "invalid_grant", error_description: "User not found." }, 400);
752
+ }
753
+ const accessToken = "microsoft_" + randomBytes(20).toString("base64url");
754
+ const newRefreshToken = "r_microsoft_" + randomBytes(20).toString("base64url");
755
+ const scopes = stored.scope ? stored.scope.split(/\s+/).filter(Boolean) : [];
756
+ if (tokenMap) {
757
+ tokenMap.set(accessToken, { login: user.email, id: user.id, scopes });
758
+ }
759
+ refreshMap.delete(refresh_token);
760
+ refreshMap.set(newRefreshToken, {
761
+ email: stored.email,
762
+ clientId: stored.clientId,
763
+ scope: stored.scope,
764
+ nonce: stored.nonce
765
+ });
766
+ const idToken = await createIdToken(user, stored.clientId || client_id, stored.nonce, baseUrl);
767
+ debug("microsoft.oauth", `[Microsoft refresh] issued new token for ${user.email}`);
768
+ return c.json({
769
+ access_token: accessToken,
770
+ token_type: "Bearer",
771
+ expires_in: 3600,
772
+ scope: stored.scope || "openid email profile",
773
+ refresh_token: newRefreshToken,
774
+ id_token: idToken
775
+ });
776
+ }
777
+ if (grant_type === "client_credentials") {
778
+ const clientsConfigured = ms.oauthClients.all().length > 0;
779
+ if (clientsConfigured) {
780
+ const client = ms.oauthClients.findOneBy("client_id", client_id);
781
+ if (!client) {
782
+ return c.json({ error: "invalid_client", error_description: "The client_id is incorrect." }, 401);
783
+ }
784
+ if (!constantTimeSecretEqual(client_secret, client.client_secret)) {
785
+ return c.json({ error: "invalid_client", error_description: "The client_secret is incorrect." }, 401);
786
+ }
787
+ }
788
+ const accessToken = "microsoft_" + randomBytes(20).toString("base64url");
789
+ const scopes = scope ? scope.split(/\s+/).filter(Boolean) : [".default"];
790
+ if (tokenMap) {
791
+ tokenMap.set(accessToken, { login: client_id, id: 0, scopes });
792
+ }
793
+ debug("microsoft.oauth", `[Microsoft client_credentials] issued token for ${client_id}`);
794
+ return c.json({
795
+ access_token: accessToken,
796
+ token_type: "Bearer",
797
+ expires_in: 3600,
798
+ scope: scope || ".default"
799
+ });
800
+ }
801
+ return c.json(
802
+ {
803
+ error: "unsupported_grant_type",
804
+ error_description: "Only authorization_code, refresh_token, and client_credentials are supported."
805
+ },
806
+ 400
807
+ );
808
+ });
809
+ app.get("/oidc/userinfo", (c) => {
810
+ const authUser = c.get("authUser");
811
+ if (!authUser) {
812
+ return c.json({ error: "invalid_token", error_description: "Authentication required." }, 401);
813
+ }
814
+ const user = ms.users.findOneBy("email", authUser.login);
815
+ if (!user) {
816
+ return c.json({ error: "invalid_token", error_description: "User not found." }, 401);
817
+ }
818
+ return c.json({
819
+ sub: user.oid,
820
+ email: user.email,
821
+ name: user.name,
822
+ given_name: user.given_name,
823
+ family_name: user.family_name,
824
+ preferred_username: user.preferred_username
825
+ });
826
+ });
827
+ app.get("/v1.0/me", (c) => {
828
+ const authUser = c.get("authUser");
829
+ if (!authUser) {
830
+ return c.json({ error: { code: "InvalidAuthenticationToken", message: "Authentication required." } }, 401);
831
+ }
832
+ const user = ms.users.findOneBy("email", authUser.login);
833
+ if (!user) {
834
+ return c.json({ error: { code: "Request_ResourceNotFound", message: "User not found." } }, 404);
835
+ }
836
+ return c.json({
837
+ "@odata.context": `${baseUrl}/v1.0/$metadata#users/$entity`,
838
+ id: user.oid,
839
+ displayName: user.name,
840
+ givenName: user.given_name,
841
+ surname: user.family_name,
842
+ mail: user.email,
843
+ userPrincipalName: user.preferred_username
844
+ });
845
+ });
846
+ app.post("/:tenant/oauth2/token", async (c) => {
847
+ const contentType = c.req.header("Content-Type") ?? "";
848
+ const rawText = await c.req.text();
849
+ let body;
850
+ if (contentType.includes("application/json")) {
851
+ try {
852
+ body = JSON.parse(rawText);
853
+ } catch {
854
+ body = {};
855
+ }
856
+ } else {
857
+ body = Object.fromEntries(new URLSearchParams(rawText));
858
+ }
859
+ const resource = typeof body.resource === "string" ? body.resource : "";
860
+ if (resource && !body.scope) {
861
+ const normalized = resource.endsWith("/") ? resource : resource + "/";
862
+ body.scope = normalized + ".default";
863
+ }
864
+ const params = new URLSearchParams();
865
+ for (const [key, value] of Object.entries(body)) {
866
+ if (key !== "resource" && typeof value === "string") {
867
+ params.set(key, value);
868
+ }
869
+ }
870
+ const v2Req = new Request(`${baseUrl}/oauth2/v2.0/token`, {
871
+ method: "POST",
872
+ headers: new Headers({
873
+ "Content-Type": "application/x-www-form-urlencoded",
874
+ ...c.req.header("Authorization") ? { Authorization: c.req.header("Authorization") } : {}
875
+ }),
876
+ body: params.toString()
877
+ });
878
+ return app.fetch(v2Req);
879
+ });
880
+ app.get("/v1.0/users/:id", (c) => {
881
+ const userId = c.req.param("id");
882
+ const user = ms.users.findOneBy("oid", userId);
883
+ if (!user) {
884
+ return c.json(
885
+ {
886
+ error: {
887
+ code: "Request_ResourceNotFound",
888
+ message: `Resource '${userId}' does not exist or one of its queried reference-property objects are not present.`
889
+ }
890
+ },
891
+ 404
892
+ );
893
+ }
894
+ return c.json({
895
+ "@odata.context": `${baseUrl}/v1.0/$metadata#users/$entity`,
896
+ id: user.oid,
897
+ displayName: user.name,
898
+ givenName: user.given_name,
899
+ surname: user.family_name,
900
+ mail: user.email,
901
+ userPrincipalName: user.preferred_username
902
+ });
903
+ });
904
+ app.get("/oauth2/v2.0/logout", (c) => {
905
+ const post_logout_redirect_uri = c.req.query("post_logout_redirect_uri");
906
+ if (post_logout_redirect_uri) {
907
+ const allClients = ms.oauthClients.all();
908
+ if (allClients.length > 0) {
909
+ const allowed = allClients.some((client) => matchesRedirectUri(post_logout_redirect_uri, client.redirect_uris));
910
+ if (!allowed) {
911
+ return c.text("Invalid post_logout_redirect_uri", 400);
912
+ }
913
+ }
914
+ return c.redirect(post_logout_redirect_uri, 302);
915
+ }
916
+ return c.text("Logged out", 200);
917
+ });
918
+ app.post("/oauth2/v2.0/revoke", async (c) => {
919
+ const contentType = c.req.header("Content-Type") ?? "";
920
+ const rawText = await c.req.text();
921
+ let token;
922
+ if (contentType.includes("application/json")) {
923
+ try {
924
+ const parsed = JSON.parse(rawText);
925
+ token = typeof parsed.token === "string" ? parsed.token : "";
926
+ } catch {
927
+ token = "";
928
+ }
929
+ } else {
930
+ const params = new URLSearchParams(rawText);
931
+ token = params.get("token") ?? "";
932
+ }
933
+ if (token && tokenMap) {
934
+ tokenMap.delete(token);
935
+ }
936
+ if (token) {
937
+ getRefreshTokens(store).delete(token);
938
+ }
939
+ return c.body(null, 200);
940
+ });
941
+ }
942
+ function openapiRoutes({ app, baseUrl }) {
943
+ app.get("/openapi.json", (c) => c.json(buildSpec(baseUrl)));
944
+ }
945
+ var jsonResponse = (description) => ({
946
+ description,
947
+ content: { "application/json": { schema: { type: "object" } } }
948
+ });
949
+ function buildSpec(baseUrl) {
950
+ return {
951
+ openapi: "3.0.3",
952
+ info: {
953
+ title: "Microsoft Graph REST API v1.0 (Emulated)",
954
+ version: "1.0.0",
955
+ description: "Emulated subset of Microsoft Graph v1.0. The OAuth security scheme uses the Microsoft Graph delegated scheme name and points at this emulator instance."
956
+ },
957
+ servers: [{ url: baseUrl }],
958
+ components: {
959
+ securitySchemes: {
960
+ azureAdDelegated: {
961
+ type: "oauth2",
962
+ description: "Microsoft identity platform delegated OAuth 2.0 authorization code flow.",
963
+ flows: {
964
+ authorizationCode: {
965
+ authorizationUrl: `${baseUrl}/oauth2/v2.0/authorize`,
966
+ tokenUrl: `${baseUrl}/oauth2/v2.0/token`,
967
+ scopes: {
968
+ openid: "Sign users in.",
969
+ email: "View users' email address.",
970
+ profile: "View users' basic profile.",
971
+ offline_access: "Maintain access to data you have given it access to.",
972
+ "User.Read": "Sign in and read user profile."
973
+ }
974
+ }
975
+ }
976
+ }
977
+ }
978
+ },
979
+ security: [{ azureAdDelegated: ["User.Read"] }],
980
+ paths: {
981
+ "/v1.0/me": {
982
+ get: {
983
+ operationId: "graphUser_GetMyProfile",
984
+ summary: "Get the signed-in user",
985
+ security: [{ azureAdDelegated: ["User.Read"] }],
986
+ responses: {
987
+ "200": jsonResponse("Graph user profile."),
988
+ "401": jsonResponse("Authentication is required.")
989
+ }
990
+ }
991
+ },
992
+ "/v1.0/users/{id}": {
993
+ get: {
994
+ operationId: "graphUser_GetById",
995
+ summary: "Get a user by id or user principal name",
996
+ parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
997
+ security: [{ azureAdDelegated: ["User.Read"] }],
998
+ responses: {
999
+ "200": jsonResponse("Graph user profile."),
1000
+ "401": jsonResponse("Authentication is required."),
1001
+ "404": jsonResponse("User not found.")
1002
+ }
1003
+ }
1004
+ }
1005
+ }
1006
+ };
1007
+ }
1008
+ var manifest = {
1009
+ id: "microsoft",
1010
+ name: "Microsoft Entra ID",
1011
+ description: "Stateful Microsoft Entra ID emulator for OAuth 2.0, OpenID Connect, Graph /me, logout, and token flows.",
1012
+ docsUrl: "https://docs.emulators.dev/microsoft",
1013
+ surfaces: [
1014
+ { id: "rest", kind: "rest", title: "Microsoft Graph (subset)", status: "partial", basePath: "/v1.0" },
1015
+ { id: "oauth", kind: "oauth", title: "Microsoft OAuth 2.0", status: "supported", basePath: "/oauth2/v2.0" },
1016
+ { id: "oidc", kind: "oidc", title: "OpenID Connect", status: "supported", basePath: "/.well-known" }
1017
+ ],
1018
+ auth: [
1019
+ { id: "oauth-code", title: "OAuth authorization code", type: "oauth-authorization-code", status: "supported" },
1020
+ {
1021
+ id: "client-credentials",
1022
+ title: "OAuth client credentials",
1023
+ type: "oauth-client-credentials",
1024
+ status: "supported"
1025
+ },
1026
+ { id: "oidc", title: "OIDC identity tokens", type: "oidc", status: "supported" }
1027
+ ],
1028
+ specs: [
1029
+ {
1030
+ kind: "openapi",
1031
+ title: "Microsoft Graph v1.0 subset",
1032
+ coverage: "hand-authored",
1033
+ url: "/openapi.json",
1034
+ operations: [
1035
+ { operationId: "graphUser_GetMyProfile", method: "GET", path: "/v1.0/me", status: "hand-authored" },
1036
+ { operationId: "graphUser_GetById", method: "GET", path: "/v1.0/users/:id", status: "hand-authored" },
1037
+ { operationId: "graphUser_List", method: "GET", path: "/v1.0/users", status: "unsupported" },
1038
+ {
1039
+ operationId: "message_List",
1040
+ method: "GET",
1041
+ path: "/v1.0/me/messages",
1042
+ status: "unsupported"
1043
+ }
1044
+ ]
1045
+ },
1046
+ {
1047
+ kind: "oauth-metadata",
1048
+ title: "Microsoft OIDC metadata",
1049
+ coverage: "hand-authored",
1050
+ operations: [
1051
+ {
1052
+ operationId: "oidc/openidConfiguration",
1053
+ method: "GET",
1054
+ path: "/.well-known/openid-configuration",
1055
+ status: "hand-authored"
1056
+ },
1057
+ {
1058
+ operationId: "oidc/tenantOpenidConfiguration",
1059
+ method: "GET",
1060
+ path: "/:tenant/v2.0/.well-known/openid-configuration",
1061
+ status: "hand-authored"
1062
+ },
1063
+ { operationId: "oidc/jwks", method: "GET", path: "/discovery/v2.0/keys", status: "hand-authored" },
1064
+ { operationId: "oauth/authorize", method: "GET", path: "/oauth2/v2.0/authorize", status: "hand-authored" },
1065
+ {
1066
+ operationId: "oauth/authorizeCallback",
1067
+ method: "POST",
1068
+ path: "/oauth2/v2.0/authorize/callback",
1069
+ status: "hand-authored"
1070
+ },
1071
+ {
1072
+ operationId: "oauth/token",
1073
+ method: "POST",
1074
+ path: "/oauth2/v2.0/token",
1075
+ status: "hand-authored",
1076
+ summary: "authorization_code, refresh_token, and client_credentials grants."
1077
+ },
1078
+ {
1079
+ operationId: "oauth/tokenV1",
1080
+ method: "POST",
1081
+ path: "/:tenant/oauth2/token",
1082
+ status: "hand-authored",
1083
+ summary: "Legacy Azure AD v1 token endpoint that translates resource to scope."
1084
+ },
1085
+ { operationId: "oauth/logout", method: "GET", path: "/oauth2/v2.0/logout", status: "hand-authored" },
1086
+ { operationId: "oauth/revoke", method: "POST", path: "/oauth2/v2.0/revoke", status: "hand-authored" },
1087
+ { operationId: "oidc/userinfo", method: "GET", path: "/oidc/userinfo", status: "hand-authored" }
1088
+ ]
1089
+ },
1090
+ {
1091
+ kind: "manual",
1092
+ title: "Microsoft Graph behavior",
1093
+ coverage: "partial",
1094
+ operations: [
1095
+ { operationId: "graph/me", method: "GET", path: "/v1.0/me", status: "hand-authored" },
1096
+ { operationId: "graph/getUser", method: "GET", path: "/v1.0/users/:id", status: "hand-authored" },
1097
+ { operationId: "graph/listUsers", method: "GET", path: "/v1.0/users", status: "unsupported" },
1098
+ {
1099
+ operationId: "graph/listMessages",
1100
+ method: "GET",
1101
+ path: "/v1.0/me/messages",
1102
+ status: "unsupported"
1103
+ }
1104
+ ]
1105
+ }
1106
+ ],
1107
+ seedSchema: {
1108
+ description: "Seed Entra ID users and registered OAuth client applications.",
1109
+ fields: [
1110
+ {
1111
+ key: "users",
1112
+ title: "Users",
1113
+ description: "Directory users addressable by email and selectable on the sign-in page.",
1114
+ example: [{ email: "testuser@outlook.com", name: "Test User" }]
1115
+ },
1116
+ {
1117
+ key: "oauth_clients",
1118
+ title: "OAuth clients",
1119
+ description: "Registered application registrations with client secrets and redirect URIs.",
1120
+ example: [
1121
+ {
1122
+ client_id: "example-client-id",
1123
+ client_secret: "example-client-secret",
1124
+ name: "My Microsoft App",
1125
+ redirect_uris: ["http://localhost:3000/api/auth/callback/microsoft-entra-id"]
1126
+ }
1127
+ ]
1128
+ }
1129
+ ],
1130
+ example: {
1131
+ users: [{ email: "testuser@outlook.com", name: "Test User" }],
1132
+ oauth_clients: [
1133
+ {
1134
+ client_id: "example-client-id",
1135
+ client_secret: "example-client-secret",
1136
+ name: "My Microsoft App",
1137
+ redirect_uris: ["http://localhost:3000/api/auth/callback/microsoft-entra-id"]
1138
+ }
1139
+ ]
1140
+ }
1141
+ },
1142
+ stateModel: {
1143
+ description: "Entities mutated by Microsoft provider calls.",
1144
+ collections: [{ name: "microsoft.users" }, { name: "microsoft.oauth_clients" }]
1145
+ },
1146
+ connections: [
1147
+ {
1148
+ id: "msal-node",
1149
+ title: "MSAL Node (TypeScript)",
1150
+ kind: "sdk",
1151
+ language: "typescript",
1152
+ description: "Point MSAL at the emulator by overriding the Entra authority host.",
1153
+ template: 'import { ConfidentialClientApplication } from "@azure/msal-node";\n\nconst app = new ConfidentialClientApplication({\n auth: {\n clientId: "{{clientId}}",\n clientSecret: "{{clientSecret}}",\n authority: "{{baseUrl}}/common/v2.0",\n },\n system: {\n networkClient: undefined,\n },\n});\n\n// Authority validation must be disabled for the emulator host.\nconst token = await app.acquireTokenByClientCredential({\n scopes: ["https://graph.microsoft.com/.default"],\n});'
1154
+ },
1155
+ {
1156
+ id: "graph-client",
1157
+ title: "Microsoft Graph client (fetch)",
1158
+ kind: "sdk",
1159
+ language: "typescript",
1160
+ description: "Call the Graph /me endpoint with a bearer token from the emulator.",
1161
+ template: 'const res = await fetch("{{baseUrl}}/v1.0/me", {\n headers: { authorization: "Bearer {{token}}" },\n});\nconst me = await res.json();'
1162
+ },
1163
+ {
1164
+ id: "ms-env",
1165
+ title: "Entra ID environment (env)",
1166
+ kind: "env",
1167
+ language: "bash",
1168
+ description: "Override the Entra authority and Graph base URLs to point at the emulator.",
1169
+ template: "AZURE_AUTHORITY_HOST={{baseUrl}}\nAZURE_TENANT_ID=common\nAZURE_CLIENT_ID={{clientId}}\nAZURE_CLIENT_SECRET={{clientSecret}}\nMICROSOFT_GRAPH_ENDPOINT={{baseUrl}}/v1.0"
1170
+ },
1171
+ {
1172
+ id: "curl-discovery",
1173
+ title: "curl (OIDC discovery)",
1174
+ kind: "curl",
1175
+ language: "bash",
1176
+ description: "Fetch the OpenID configuration the emulator serves.",
1177
+ template: "curl -s {{baseUrl}}/.well-known/openid-configuration"
1178
+ },
1179
+ {
1180
+ id: "curl-graph-me",
1181
+ title: "curl (Graph /me)",
1182
+ kind: "curl",
1183
+ language: "bash",
1184
+ description: "Call Microsoft Graph /me with a bearer token.",
1185
+ template: 'curl -s {{baseUrl}}/v1.0/me -H "authorization: Bearer {{token}}"'
1186
+ }
1187
+ ]
1188
+ };
1189
+ function seedDefaults(store, _baseUrl) {
1190
+ const ms = getMicrosoftStore(store);
1191
+ ms.users.insert({
1192
+ oid: generateOid(),
1193
+ email: "testuser@outlook.com",
1194
+ name: "Test User",
1195
+ given_name: "Test",
1196
+ family_name: "User",
1197
+ email_verified: true,
1198
+ tenant_id: DEFAULT_TENANT_ID,
1199
+ preferred_username: "testuser@outlook.com"
1200
+ });
1201
+ }
1202
+ function seedFromConfig(store, _baseUrl, config) {
1203
+ const ms = getMicrosoftStore(store);
1204
+ if (config.users) {
1205
+ for (const u of config.users) {
1206
+ const existing = ms.users.findOneBy("email", u.email);
1207
+ if (existing) continue;
1208
+ const nameParts = (u.name ?? "").split(/\s+/);
1209
+ ms.users.insert({
1210
+ oid: generateOid(),
1211
+ email: u.email,
1212
+ name: u.name ?? u.email.split("@")[0],
1213
+ given_name: u.given_name ?? nameParts[0] ?? "",
1214
+ family_name: u.family_name ?? nameParts.slice(1).join(" ") ?? "",
1215
+ email_verified: true,
1216
+ tenant_id: u.tenant_id ?? DEFAULT_TENANT_ID,
1217
+ preferred_username: u.email
1218
+ });
1219
+ }
1220
+ }
1221
+ if (config.oauth_clients) {
1222
+ for (const client of config.oauth_clients) {
1223
+ const existing = ms.oauthClients.findOneBy("client_id", client.client_id);
1224
+ if (existing) continue;
1225
+ ms.oauthClients.insert({
1226
+ client_id: client.client_id,
1227
+ client_secret: client.client_secret,
1228
+ name: client.name,
1229
+ redirect_uris: client.redirect_uris,
1230
+ tenant_id: client.tenant_id ?? DEFAULT_TENANT_ID
1231
+ });
1232
+ }
1233
+ }
1234
+ }
1235
+ var microsoftPlugin = {
1236
+ name: "microsoft",
1237
+ register(app, store, webhooks, baseUrl, tokenMap) {
1238
+ const ctx = { app, store, webhooks, baseUrl, tokenMap };
1239
+ oauthRoutes(ctx);
1240
+ openapiRoutes(ctx);
1241
+ },
1242
+ seed(store, baseUrl) {
1243
+ seedDefaults(store, baseUrl);
1244
+ }
1245
+ };
1246
+ var index_default = microsoftPlugin;
1247
+ export {
1248
+ index_default as default,
1249
+ getMicrosoftStore,
1250
+ manifest,
1251
+ microsoftPlugin,
1252
+ seedFromConfig
1253
+ };
1254
+ //# sourceMappingURL=dist-M3GVASMR.js.map