@fruggr/zendesk-mcp-server 3.1.2 → 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/THIRD-PARTY-NOTICES.md +6 -22
- package/dist/{http-CmtU-oNu.js → http-7NVoxfEA.js} +189 -81
- package/dist/index.js +1412 -464
- package/dist/sbom.cdx.json +19 -31
- package/dist/{token-store-CLtwRaxI.js → token-store-SYK0DN9M.js} +42 -61
- package/package.json +3 -3
|
@@ -30,7 +30,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
30
30
|
SOFTWARE.
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
## @modelcontextprotocol/core@2.
|
|
33
|
+
## @modelcontextprotocol/core@2.2.0
|
|
34
34
|
|
|
35
35
|
License: MIT
|
|
36
36
|
|
|
@@ -476,7 +476,7 @@ CC-BY-4.0. See https://creativecommons.org/licenses/by/4.0/legalcode for
|
|
|
476
476
|
the full license text.
|
|
477
477
|
```
|
|
478
478
|
|
|
479
|
-
## @modelcontextprotocol/server@2.
|
|
479
|
+
## @modelcontextprotocol/server@2.2.0
|
|
480
480
|
|
|
481
481
|
License: MIT
|
|
482
482
|
|
|
@@ -2774,14 +2774,14 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
|
2774
2774
|
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
2775
2775
|
```
|
|
2776
2776
|
|
|
2777
|
-
## mdast-util-gfm-strikethrough@2.0.
|
|
2777
|
+
## mdast-util-gfm-strikethrough@2.0.1
|
|
2778
2778
|
|
|
2779
2779
|
License: MIT
|
|
2780
2780
|
|
|
2781
2781
|
```
|
|
2782
2782
|
(The MIT License)
|
|
2783
2783
|
|
|
2784
|
-
Copyright (c)
|
|
2784
|
+
Copyright (c) Titus Wormer <tituswormer@gmail.com>
|
|
2785
2785
|
|
|
2786
2786
|
Permission is hereby granted, free of charge, to any person obtaining
|
|
2787
2787
|
a copy of this software and associated documentation files (the
|
|
@@ -2920,7 +2920,7 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
|
2920
2920
|
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
2921
2921
|
```
|
|
2922
2922
|
|
|
2923
|
-
## mdast-util-to-markdown@2.1.
|
|
2923
|
+
## mdast-util-to-markdown@2.1.3
|
|
2924
2924
|
|
|
2925
2925
|
License: MIT
|
|
2926
2926
|
|
|
@@ -4111,22 +4111,6 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
|
4111
4111
|
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
4112
4112
|
```
|
|
4113
4113
|
|
|
4114
|
-
## powershell-utils@0.1.0
|
|
4115
|
-
|
|
4116
|
-
License: MIT
|
|
4117
|
-
|
|
4118
|
-
```
|
|
4119
|
-
MIT License
|
|
4120
|
-
|
|
4121
|
-
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
|
|
4122
|
-
|
|
4123
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4124
|
-
|
|
4125
|
-
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
4126
|
-
|
|
4127
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
4128
|
-
```
|
|
4129
|
-
|
|
4130
4114
|
## powershell-utils@0.2.1
|
|
4131
4115
|
|
|
4132
4116
|
License: MIT
|
|
@@ -5198,7 +5182,7 @@ The above copyright notice and this permission notice shall be included in all c
|
|
|
5198
5182
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
5199
5183
|
```
|
|
5200
5184
|
|
|
5201
|
-
## wsl-utils@1.0.
|
|
5185
|
+
## wsl-utils@1.0.1
|
|
5202
5186
|
|
|
5203
5187
|
License: MIT
|
|
5204
5188
|
|
|
@@ -16,7 +16,7 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
16
16
|
import { accessSync, chmodSync, constants, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
17
17
|
import { Http2ServerRequest, constants as constants$1 } from "http2";
|
|
18
18
|
import crypto$1 from "crypto";
|
|
19
|
-
//#region node_modules/.pnpm/@hono+node-server@1.19.17_hono@4.13.
|
|
19
|
+
//#region node_modules/.pnpm/@hono+node-server@1.19.17_hono@4.13.12/node_modules/@hono/node-server/dist/index.mjs
|
|
20
20
|
var RequestError = class extends Error {
|
|
21
21
|
constructor(message, options) {
|
|
22
22
|
super(message, options);
|
|
@@ -504,7 +504,7 @@ var getRequestListener = (fetchCallback, options = {}) => {
|
|
|
504
504
|
};
|
|
505
505
|
};
|
|
506
506
|
//#endregion
|
|
507
|
-
//#region node_modules/.pnpm/@modelcontextprotocol+node@2.1.0_@modelcontextprotocol+server@2.
|
|
507
|
+
//#region node_modules/.pnpm/@modelcontextprotocol+node@2.1.0_@modelcontextprotocol+server@2.2.0_hono@4.13.12/node_modules/@modelcontextprotocol/node/dist/index.mjs
|
|
508
508
|
/**
|
|
509
509
|
* Server transport for Streamable HTTP: this implements the MCP Streamable HTTP transport specification.
|
|
510
510
|
* It supports both SSE streaming and direct HTTP responses.
|
|
@@ -2264,82 +2264,6 @@ function decodeJwt(jwt) {
|
|
|
2264
2264
|
return result;
|
|
2265
2265
|
}
|
|
2266
2266
|
//#endregion
|
|
2267
|
-
//#region src/auth/server/expiring-map.ts
|
|
2268
|
-
/**
|
|
2269
|
-
* An in-memory map whose entries lapse after their own TTL. Expired entries are
|
|
2270
|
-
* swept on every `set`, so the map stays bounded by what was set within the
|
|
2271
|
-
* longest TTL, with no timer to hold the process open.
|
|
2272
|
-
*/
|
|
2273
|
-
const createExpiringMap = () => {
|
|
2274
|
-
const entries = /* @__PURE__ */ new Map();
|
|
2275
|
-
const get = (key) => {
|
|
2276
|
-
const entry = entries.get(key);
|
|
2277
|
-
return entry && entry.expiresAt > Date.now() ? entry.value : void 0;
|
|
2278
|
-
};
|
|
2279
|
-
return {
|
|
2280
|
-
set: (key, value, ttlMs) => {
|
|
2281
|
-
const now = Date.now();
|
|
2282
|
-
for (const [k, entry] of entries) if (entry.expiresAt <= now) entries.delete(k);
|
|
2283
|
-
entries.set(key, {
|
|
2284
|
-
value,
|
|
2285
|
-
expiresAt: now + ttlMs
|
|
2286
|
-
});
|
|
2287
|
-
},
|
|
2288
|
-
get,
|
|
2289
|
-
/** Read and forget: a pending value is used at most once. */
|
|
2290
|
-
take: (key) => {
|
|
2291
|
-
const value = get(key);
|
|
2292
|
-
entries.delete(key);
|
|
2293
|
-
return value;
|
|
2294
|
-
},
|
|
2295
|
-
/** Live and not-yet-swept entries. */
|
|
2296
|
-
size: () => entries.size
|
|
2297
|
-
};
|
|
2298
|
-
};
|
|
2299
|
-
//#endregion
|
|
2300
|
-
//#region src/auth/server/consent.ts
|
|
2301
|
-
const hostOf = (uri) => {
|
|
2302
|
-
try {
|
|
2303
|
-
return new URL(uri).host;
|
|
2304
|
-
} catch {
|
|
2305
|
-
return uri;
|
|
2306
|
-
}
|
|
2307
|
-
};
|
|
2308
|
-
const SCOPE_LABELS = {
|
|
2309
|
-
read: "Read your Zendesk data",
|
|
2310
|
-
write: "Create and change Zendesk data on your behalf"
|
|
2311
|
-
};
|
|
2312
|
-
/**
|
|
2313
|
-
* The MCP consent screen, shown once per client that is not trusted (DCR,
|
|
2314
|
-
* unknown CIMD, loopback redirects). Names the client, the scopes and where the
|
|
2315
|
-
* code goes, per the spec's UI requirements. Every interpolated value is
|
|
2316
|
-
* escaped: the client name comes from a document anyone can publish.
|
|
2317
|
-
*/
|
|
2318
|
-
const renderConsentPage = (view) => {
|
|
2319
|
-
const name = escapeHtml(view.clientName ?? view.clientId);
|
|
2320
|
-
const scopes = view.scopes.map((scope) => `<li>${escapeHtml(SCOPE_LABELS[scope] ?? scope)}</li>`).join("");
|
|
2321
|
-
const action = `/interaction/${encodeURIComponent(view.uid)}`;
|
|
2322
|
-
return [
|
|
2323
|
-
"<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\">",
|
|
2324
|
-
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">",
|
|
2325
|
-
"<title>Authorize access to Zendesk</title>",
|
|
2326
|
-
"<style>body{font-family:system-ui,sans-serif;max-width:32rem;margin:3rem auto;padding:0 1rem;line-height:1.5}",
|
|
2327
|
-
"code{word-break:break-all}button{font:inherit;padding:.5rem 1.25rem;margin-right:.5rem}</style>",
|
|
2328
|
-
"</head><body>",
|
|
2329
|
-
`<h1>Allow ${name} to use your Zendesk account?</h1>`,
|
|
2330
|
-
`<p><strong>${name}</strong> is asking to act as you on <strong>${escapeHtml(view.subdomain)}.zendesk.com</strong>:</p>`,
|
|
2331
|
-
`<ul>${scopes}</ul>`,
|
|
2332
|
-
`<p>After you allow it, you are sent back to <strong>${escapeHtml(hostOf(view.redirectUri))}</strong>`,
|
|
2333
|
-
` (<code>${escapeHtml(view.redirectUri)}</code>). Only continue if you started this connection yourself.</p>`,
|
|
2334
|
-
`<p>Client id: <code>${escapeHtml(view.clientId)}</code></p>`,
|
|
2335
|
-
`<form method="post" action="${action}/confirm" style="display:inline"><button type="submit">Allow</button></form>`,
|
|
2336
|
-
`<form method="post" action="${action}/abort" style="display:inline"><button type="submit">Deny</button></form>`,
|
|
2337
|
-
"</body></html>"
|
|
2338
|
-
].join("");
|
|
2339
|
-
};
|
|
2340
|
-
/** Plain error page for a failed sign-in. ASCII message, escaped. */
|
|
2341
|
-
const renderErrorPage = (title, detail) => `<!doctype html><html lang="en"><head><meta charset="utf-8"><title>Sign-in failed</title></head><body><h1>${escapeHtml(title)}</h1><p>${escapeHtml(detail)}</p></body></html>`;
|
|
2342
|
-
//#endregion
|
|
2343
2267
|
//#region src/auth/server/trusted-clients.ts
|
|
2344
2268
|
/**
|
|
2345
2269
|
* Which CIMD clients skip our consent screen, and the fetch shim applied to CIMD
|
|
@@ -2425,6 +2349,184 @@ const createCimdFetch = (baseFetch) => async (url, options) => {
|
|
|
2425
2349
|
});
|
|
2426
2350
|
};
|
|
2427
2351
|
//#endregion
|
|
2352
|
+
//#region src/auth/server/cimd-documents.ts
|
|
2353
|
+
/** How long a kept copy may stand in for its document, after it was kept. */
|
|
2354
|
+
const LAST_GOOD_TTL_S = 604800;
|
|
2355
|
+
const STALE_MAX_AGE_S = 60;
|
|
2356
|
+
const CIMD_FETCH_HINT = "A client document, or a URL it names, could not be fetched: the client cannot sign in or refresh unless a last good copy is served. See https://github.com/fruggr/zendesk-mcp-server/blob/main/docs/troubleshooting.md#http-the-log-shows-oauth_client_fetch_failed";
|
|
2357
|
+
const isTransientFailure = (status) => status === 403 || status === 408 || status === 429 || status >= 500;
|
|
2358
|
+
const messageOf = (err) => {
|
|
2359
|
+
if (!(err instanceof Error)) return String(err);
|
|
2360
|
+
return err.cause instanceof Error ? `${err.message}: ${err.cause.message}` : err.message;
|
|
2361
|
+
};
|
|
2362
|
+
const ownDocument = async (response, url) => {
|
|
2363
|
+
const parsed = await response.json().catch(() => void 0);
|
|
2364
|
+
return parsed?.client_id === url ? parsed : void 0;
|
|
2365
|
+
};
|
|
2366
|
+
/**
|
|
2367
|
+
* A copy is kept when a token is issued, from the client oidc-provider built
|
|
2368
|
+
* and validated: an unauthenticated caller can make the server fetch any URL,
|
|
2369
|
+
* but not write to the grant store, and a rejected document is never kept.
|
|
2370
|
+
*/
|
|
2371
|
+
const createCimdDocuments = (baseFetch, { lastGood, logger }) => {
|
|
2372
|
+
const rewrite = createCimdFetch(baseFetch);
|
|
2373
|
+
const stale = /* @__PURE__ */ new Set();
|
|
2374
|
+
const kept = (url) => lastGood.get(url).catch((err) => {
|
|
2375
|
+
logger.warn("oauth_cimd_last_good_read_failed", {
|
|
2376
|
+
url,
|
|
2377
|
+
error: messageOf(err)
|
|
2378
|
+
});
|
|
2379
|
+
});
|
|
2380
|
+
const report = (url, failure, copy) => {
|
|
2381
|
+
logger.warn("oauth_client_fetch_failed", {
|
|
2382
|
+
url,
|
|
2383
|
+
...failure,
|
|
2384
|
+
fallback: copy ? "last_good" : "none",
|
|
2385
|
+
...copy && { ageS: Math.floor((Date.now() - copy.fetchedAt) / 1e3) },
|
|
2386
|
+
hint: CIMD_FETCH_HINT
|
|
2387
|
+
});
|
|
2388
|
+
};
|
|
2389
|
+
const serve = (url, copy) => {
|
|
2390
|
+
stale.add(url);
|
|
2391
|
+
return Response.json(copy.document, { headers: { "cache-control": `max-age=${STALE_MAX_AGE_S}` } });
|
|
2392
|
+
};
|
|
2393
|
+
const fallBack = async (url, failure, failed) => {
|
|
2394
|
+
const copy = await kept(url);
|
|
2395
|
+
report(url, failure, copy);
|
|
2396
|
+
if (!copy) return void 0;
|
|
2397
|
+
await failed?.body?.cancel();
|
|
2398
|
+
return serve(url, copy);
|
|
2399
|
+
};
|
|
2400
|
+
const succeeded = async (url, response) => {
|
|
2401
|
+
if (await ownDocument(response.clone(), url) && response.status === 200) {
|
|
2402
|
+
stale.delete(url);
|
|
2403
|
+
return response;
|
|
2404
|
+
}
|
|
2405
|
+
const copy = await kept(url);
|
|
2406
|
+
if (!copy) return response;
|
|
2407
|
+
report(url, {
|
|
2408
|
+
status: response.status,
|
|
2409
|
+
error: "not a client document"
|
|
2410
|
+
}, copy);
|
|
2411
|
+
await response.body?.cancel();
|
|
2412
|
+
return serve(url, copy);
|
|
2413
|
+
};
|
|
2414
|
+
const fetch = async (input, init) => {
|
|
2415
|
+
const url = String(input);
|
|
2416
|
+
let response;
|
|
2417
|
+
try {
|
|
2418
|
+
response = await rewrite(input, init);
|
|
2419
|
+
} catch (err) {
|
|
2420
|
+
const copy = await fallBack(url, { error: messageOf(err) });
|
|
2421
|
+
if (copy) return copy;
|
|
2422
|
+
throw err;
|
|
2423
|
+
}
|
|
2424
|
+
if (response.ok) return succeeded(url, response);
|
|
2425
|
+
if (!isTransientFailure(response.status)) {
|
|
2426
|
+
logger.debug("oauth_client_fetch_failed", {
|
|
2427
|
+
url,
|
|
2428
|
+
status: response.status,
|
|
2429
|
+
fallback: "none"
|
|
2430
|
+
});
|
|
2431
|
+
return response;
|
|
2432
|
+
}
|
|
2433
|
+
return await fallBack(url, { status: response.status }, response) ?? response;
|
|
2434
|
+
};
|
|
2435
|
+
return {
|
|
2436
|
+
fetch,
|
|
2437
|
+
granted: async (client) => {
|
|
2438
|
+
if (client.clientIdMetadataDocument !== true || stale.has(client.clientId)) return;
|
|
2439
|
+
try {
|
|
2440
|
+
await lastGood.set(client.clientId, {
|
|
2441
|
+
document: client.metadata(),
|
|
2442
|
+
fetchedAt: Date.now()
|
|
2443
|
+
}, LAST_GOOD_TTL_S);
|
|
2444
|
+
} catch (err) {
|
|
2445
|
+
logger.warn("oauth_cimd_last_good_store_failed", {
|
|
2446
|
+
url: client.clientId,
|
|
2447
|
+
error: messageOf(err)
|
|
2448
|
+
});
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2451
|
+
};
|
|
2452
|
+
};
|
|
2453
|
+
//#endregion
|
|
2454
|
+
//#region src/auth/server/expiring-map.ts
|
|
2455
|
+
/**
|
|
2456
|
+
* An in-memory map whose entries lapse after their own TTL. Expired entries are
|
|
2457
|
+
* swept on every `set`, so the map stays bounded by what was set within the
|
|
2458
|
+
* longest TTL, with no timer to hold the process open.
|
|
2459
|
+
*/
|
|
2460
|
+
const createExpiringMap = () => {
|
|
2461
|
+
const entries = /* @__PURE__ */ new Map();
|
|
2462
|
+
const get = (key) => {
|
|
2463
|
+
const entry = entries.get(key);
|
|
2464
|
+
return entry && entry.expiresAt > Date.now() ? entry.value : void 0;
|
|
2465
|
+
};
|
|
2466
|
+
return {
|
|
2467
|
+
set: (key, value, ttlMs) => {
|
|
2468
|
+
const now = Date.now();
|
|
2469
|
+
for (const [k, entry] of entries) if (entry.expiresAt <= now) entries.delete(k);
|
|
2470
|
+
entries.set(key, {
|
|
2471
|
+
value,
|
|
2472
|
+
expiresAt: now + ttlMs
|
|
2473
|
+
});
|
|
2474
|
+
},
|
|
2475
|
+
get,
|
|
2476
|
+
/** Read and forget: a pending value is used at most once. */
|
|
2477
|
+
take: (key) => {
|
|
2478
|
+
const value = get(key);
|
|
2479
|
+
entries.delete(key);
|
|
2480
|
+
return value;
|
|
2481
|
+
},
|
|
2482
|
+
/** Live and not-yet-swept entries. */
|
|
2483
|
+
size: () => entries.size
|
|
2484
|
+
};
|
|
2485
|
+
};
|
|
2486
|
+
//#endregion
|
|
2487
|
+
//#region src/auth/server/consent.ts
|
|
2488
|
+
const hostOf = (uri) => {
|
|
2489
|
+
try {
|
|
2490
|
+
return new URL(uri).host;
|
|
2491
|
+
} catch {
|
|
2492
|
+
return uri;
|
|
2493
|
+
}
|
|
2494
|
+
};
|
|
2495
|
+
const SCOPE_LABELS = {
|
|
2496
|
+
read: "Read your Zendesk data",
|
|
2497
|
+
write: "Create and change Zendesk data on your behalf"
|
|
2498
|
+
};
|
|
2499
|
+
/**
|
|
2500
|
+
* The MCP consent screen, shown once per client that is not trusted (DCR,
|
|
2501
|
+
* unknown CIMD, loopback redirects). Names the client, the scopes and where the
|
|
2502
|
+
* code goes, per the spec's UI requirements. Every interpolated value is
|
|
2503
|
+
* escaped: the client name comes from a document anyone can publish.
|
|
2504
|
+
*/
|
|
2505
|
+
const renderConsentPage = (view) => {
|
|
2506
|
+
const name = escapeHtml(view.clientName ?? view.clientId);
|
|
2507
|
+
const scopes = view.scopes.map((scope) => `<li>${escapeHtml(SCOPE_LABELS[scope] ?? scope)}</li>`).join("");
|
|
2508
|
+
const action = `/interaction/${encodeURIComponent(view.uid)}`;
|
|
2509
|
+
return [
|
|
2510
|
+
"<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\">",
|
|
2511
|
+
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">",
|
|
2512
|
+
"<title>Authorize access to Zendesk</title>",
|
|
2513
|
+
"<style>body{font-family:system-ui,sans-serif;max-width:32rem;margin:3rem auto;padding:0 1rem;line-height:1.5}",
|
|
2514
|
+
"code{word-break:break-all}button{font:inherit;padding:.5rem 1.25rem;margin-right:.5rem}</style>",
|
|
2515
|
+
"</head><body>",
|
|
2516
|
+
`<h1>Allow ${name} to use your Zendesk account?</h1>`,
|
|
2517
|
+
`<p><strong>${name}</strong> is asking to act as you on <strong>${escapeHtml(view.subdomain)}.zendesk.com</strong>:</p>`,
|
|
2518
|
+
`<ul>${scopes}</ul>`,
|
|
2519
|
+
`<p>After you allow it, you are sent back to <strong>${escapeHtml(hostOf(view.redirectUri))}</strong>`,
|
|
2520
|
+
` (<code>${escapeHtml(view.redirectUri)}</code>). Only continue if you started this connection yourself.</p>`,
|
|
2521
|
+
`<p>Client id: <code>${escapeHtml(view.clientId)}</code></p>`,
|
|
2522
|
+
`<form method="post" action="${action}/confirm" style="display:inline"><button type="submit">Allow</button></form>`,
|
|
2523
|
+
`<form method="post" action="${action}/abort" style="display:inline"><button type="submit">Deny</button></form>`,
|
|
2524
|
+
"</body></html>"
|
|
2525
|
+
].join("");
|
|
2526
|
+
};
|
|
2527
|
+
/** Plain error page for a failed sign-in. ASCII message, escaped. */
|
|
2528
|
+
const renderErrorPage = (title, detail) => `<!doctype html><html lang="en"><head><meta charset="utf-8"><title>Sign-in failed</title></head><body><h1>${escapeHtml(title)}</h1><p>${escapeHtml(detail)}</p></body></html>`;
|
|
2529
|
+
//#endregion
|
|
2428
2530
|
//#region src/auth/server/upstream.ts
|
|
2429
2531
|
/**
|
|
2430
2532
|
* Zendesk as the upstream identity provider, through the same public PKCE client
|
|
@@ -35898,13 +36000,12 @@ const buildProvider = (options) => {
|
|
|
35898
36000
|
const { ring, resource } = options;
|
|
35899
36001
|
const [current] = ring;
|
|
35900
36002
|
const scopes = options.allowWrite ? RESOURCE_SCOPES.join(" ") : "read";
|
|
35901
|
-
const baseFetch = options.fetch ?? ((url, init) => globalThis.fetch(url, init));
|
|
35902
36003
|
const provider = new lib_default(options.issuer, {
|
|
35903
36004
|
adapter: options.adapter,
|
|
35904
36005
|
jwks: { keys: ring.map((set) => set.signingJwk) },
|
|
35905
36006
|
cookies: { keys: ring.map((set) => set.cookieKey) },
|
|
35906
36007
|
clients: [],
|
|
35907
|
-
fetch:
|
|
36008
|
+
fetch: options.cimdDocuments.fetch,
|
|
35908
36009
|
scopes: options.allowWrite ? [...RESOURCE_SCOPES, "offline_access"] : ["read", "offline_access"],
|
|
35909
36010
|
clientDefaults: {
|
|
35910
36011
|
grant_types: ["authorization_code", "refresh_token"],
|
|
@@ -36109,6 +36210,10 @@ const createAuthorizationServer = (options) => {
|
|
|
36109
36210
|
hint: "Revoked in this process only. Fix the store before restarting: its next successful write persists the removal."
|
|
36110
36211
|
});
|
|
36111
36212
|
};
|
|
36213
|
+
const cimdDocuments = createCimdDocuments(options.fetch ?? ((url, init) => globalThis.fetch(url, init)), {
|
|
36214
|
+
lastGood: createSealedCollection(persistent, "CimdDocument", ring),
|
|
36215
|
+
logger
|
|
36216
|
+
});
|
|
36112
36217
|
const provider = buildProvider({
|
|
36113
36218
|
issuer,
|
|
36114
36219
|
resource,
|
|
@@ -36118,7 +36223,10 @@ const createAuthorizationServer = (options) => {
|
|
|
36118
36223
|
allowWrite: !config.readOnly,
|
|
36119
36224
|
isAllowedOrigin: options.isAllowedOrigin,
|
|
36120
36225
|
behindProxy: config.publicUrl?.startsWith("https:") === true,
|
|
36121
|
-
|
|
36226
|
+
cimdDocuments
|
|
36227
|
+
});
|
|
36228
|
+
provider.on("grant.success", (ctx) => {
|
|
36229
|
+
if (ctx.oidc.client) cimdDocuments.granted(ctx.oidc.client);
|
|
36122
36230
|
});
|
|
36123
36231
|
provider.on("server_error", (_ctx, err) => {
|
|
36124
36232
|
logger.error("oauth_server_error", { error: err.message });
|