sap-ai-dev-toolkit 0.8.4 → 0.8.5
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/README.md +6 -2
- package/package.json +1 -1
- package/src/launcher.mjs +18 -5
- package/src/setup.mjs +42 -8
- package/src/terminal-ui.mjs +7 -5
- package/src/windows-sso-adt-proxy.mjs +72 -10
package/README.md
CHANGED
|
@@ -290,7 +290,7 @@ Supported discovery and runtime behavior:
|
|
|
290
290
|
- **Client selection:** SAP GUI entries may contain one default client but cannot reliably list every ABAP client available through ADT. Setup asks for one or more 3-digit clients per selected system; enter comma-separated values such as `100,200` to create separate MCP servers for each client.
|
|
291
291
|
- **ADT endpoint derivation:** SAP GUI entries usually contain DIAG/router information, not the HTTP(S) ADT endpoint. Setup derives the common ADT port from the SAP instance when possible, for example instance `00` → `https://host:44300`, and asks for confirmation only when configuring the selected entry.
|
|
292
292
|
- **Credential handling:** username/password local entries use VS Code MCP input prompts (`${input:...}`) at server start. Plain SAP credentials are not written to `mcp.json`.
|
|
293
|
-
- **
|
|
293
|
+
- **Login methods:** local entries can use password, browser SAML/web SSO, SAML username/password, silent Windows Negotiate/SPNEGO, Windows Negotiate/SPNEGO with native Windows credential UI/smartcard PIN, or Windows SSO with password fallback. Browser/SAML modes are passed to the VSP runtime through `SAP_BROWSER_AUTH` / `SAP_SAML_AUTH`. Windows credential UI mode runs the bundled SSPI helper interactively so Windows can display its native credential/smartcard prompt when the desktop session allows it. NTLM fallback is rejected.
|
|
294
294
|
- **IP-address HTTPS self-healing:** if the confirmed ADT URL is `https://<ip>:<port>` and the certificate is issued to DNS names, setup reads the certificate DNS SANs automatically and stores them as `SAP_TLS_SERVER_NAMES`. At runtime a loopback proxy dials the IP address but verifies TLS against those discovered DNS names, trying them one by one. Certificate verification stays enabled; there is no `--insecure` mode.
|
|
295
295
|
- **Private CAs:** if the SAP HTTPS certificate chains to an internal CA that is not trusted by the local Node runtime, set `SAP_TLS_CA_FILE` or `SAP_AI_DEV_TOOLKIT_TLS_CA_FILE` to a PEM CA bundle. This adds trust roots for the self-healing proxy without disabling certificate validation.
|
|
296
296
|
- **Redirect and request safety:** local loopback proxies reject cross-origin forward-form requests and rewrite same-origin SAP redirects back through the loopback listener, so VSP stays on the safe route.
|
|
@@ -782,10 +782,14 @@ The response contains a `tools` array. A `RunQuery` entry resembles this excerpt
|
|
|
782
782
|
| `SAP_AI_DEV_TOOLKIT_DESTINATION` | Comma-separated destination allowlist for normal runtime discovery. Setup clears this temporarily so it can display all eligible systems. |
|
|
783
783
|
| `SAP_AI_DEV_TOOLKIT_MODE` | VSP child mode (`expert` by default; `focused` omits `ActivateMultiple`, `GetUserTransports`, and `GetTransportInfo`). The proxy exposes its curated tool subset and local workflow tools, including `LintABAP`. |
|
|
784
784
|
| `SAP_ALLOW_TRANSPORTABLE_EDITS` | Generated MCP entries set this to `true` to permit source edits in transportable packages; VSP safety checks and SAP authorizations still apply. |
|
|
785
|
-
| `SAP_AUTH_MODE=windows-sso` | Local SAP GUI entry mode for experimental Windows Integrated Authentication over HTTP Negotiate/SPNEGO. Runtime starts a loopback ADT proxy
|
|
785
|
+
| `SAP_AUTH_MODE=windows-sso` | Local SAP GUI entry mode for experimental Windows Integrated Authentication over HTTP Negotiate/SPNEGO. Runtime starts a loopback ADT proxy. With `SAP_AI_DEV_TOOLKIT_WINDOWS_CREDENTIAL_UI=true`, the bundled helper is allowed to run interactively so Windows can show its native credential/smartcard PIN UI. |
|
|
786
786
|
| `SAP_TLS_SERVER_NAMES` / `SAP_AI_DEV_TOOLKIT_TLS_SERVER_NAMES` | Comma-separated local SAP GUI TLS self-healing names, normally auto-discovered during setup from the SAP HTTPS certificate when `SAP_URL` uses an IP address. Runtime tries them one by one while keeping certificate validation enabled. |
|
|
787
787
|
| `SAP_TLS_SERVER_NAME` / `SAP_AI_DEV_TOOLKIT_TLS_SERVER_NAME` | Single-name compatibility form of the local SAP GUI TLS self-healing override. Prefer the auto-discovered plural variable written by setup. |
|
|
788
788
|
| `SAP_TLS_CA_FILE` / `SAP_AI_DEV_TOOLKIT_TLS_CA_FILE` | Optional PEM CA bundle for the local TLS self-healing proxy when the SAP HTTPS certificate chains to a private CA that is not in the OS/Node trust store. Certificate verification remains enabled. |
|
|
789
|
+
| `SAP_AUTH_MODE=browser-saml` with `SAP_BROWSER_AUTH=true` / `SAP_SAML_AUTH=true` | Local browser SAML / web SSO mode. The generated entry delegates browser login handling to the VSP runtime instead of storing SAP credentials. |
|
|
790
|
+
| `SAP_AUTH_MODE=saml-password` with `SAP_SAML_AUTH=true` | Local SAML username/password mode. Generated entries use MCP input prompts for `SAP_SAML_USER` and `SAP_SAML_PASSWORD`; raw credentials are not written to `mcp.json`. |
|
|
791
|
+
| `SAP_AI_DEV_TOOLKIT_WINDOWS_CREDENTIAL_UI=true` / `SAP_WINDOWS_CREDENTIAL_UI=true` | Allows the bundled Windows SSPI helper to run without PowerShell `-NonInteractive`, enabling native Windows credential UI / smartcard PIN prompts when Windows and the MCP host desktop session permit them. |
|
|
792
|
+
| `SAP_AUTH_FALLBACK_MODE=basic` / `SAP_AI_DEV_TOOLKIT_AUTH_FALLBACK_MODE=basic` | Enables Windows SSO self-healing fallback for local SAP GUI entries. If Negotiate/SPNEGO is unavailable or the SSPI helper cannot acquire a token, the loopback proxy retries with Basic auth using `SAP_USER`/`SAP_PASSWORD` from the MCP input prompts. |
|
|
789
793
|
| `SAP_AI_DEV_TOOLKIT_NEGOTIATE_HELPER` / `SAP_AI_DEV_TOOLKIT_SSPI_HELPER` | Optional trusted Windows helper executable used by the local Windows SSO proxy to answer an HTTP `WWW-Authenticate: Negotiate` challenge with the current Windows SSPI/Kerberos/SPNEGO credentials. It is not a prompt for a user-entered token; the helper must obtain credentials from Windows. NTLM fallback is rejected because it is connection-oriented and unsafe for this proxy shape. When unset on Windows, the bundled PowerShell SSPI helper is used. A custom helper receives one JSON line on stdin (`url`, `challenge`, `mechanism`) and prints the base64 Negotiate response on stdout. |
|
|
790
794
|
| `SAP_AI_DEV_TOOLKIT_HTTP_PROXY` | Egress proxy for BAS discovery and probe traffic. Takes precedence over `HTTP_PROXY`/`http_proxy`; unset means the default BAS proxy for `.dest` hosts, empty means direct. |
|
|
791
795
|
| `SAP_AI_DEV_TOOLKIT_REQUEST_TIMEOUT_MS` | Per-request timeout for calls forwarded to a VSP child (default 600000 = 10 minutes; `0` disables). A stalled request fails with a timeout error; the child is left running. |
|
package/package.json
CHANGED
package/src/launcher.mjs
CHANGED
|
@@ -202,6 +202,22 @@ async function configuredLocalSapGuiDestination(env) {
|
|
|
202
202
|
probe: { status: tlsRoute ? 'configured-tls-server-name' : 'configured', available: true }
|
|
203
203
|
};
|
|
204
204
|
}
|
|
205
|
+
const childEnv = (() => {
|
|
206
|
+
if (authMode === 'browser-saml') {
|
|
207
|
+
return { SAP_BROWSER_AUTH: 'true', SAP_SAML_AUTH: 'true' };
|
|
208
|
+
}
|
|
209
|
+
if (authMode === 'saml-password') {
|
|
210
|
+
return {
|
|
211
|
+
SAP_SAML_AUTH: 'true',
|
|
212
|
+
SAP_SAML_USER: env.SAP_SAML_USER || env.SAP_USER || env.SAP_USERNAME || '',
|
|
213
|
+
SAP_SAML_PASSWORD: env.SAP_SAML_PASSWORD || env.SAP_PASSWORD || env.SAP_PASS || ''
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
SAP_USER: env.SAP_USER || env.SAP_USERNAME || '',
|
|
218
|
+
SAP_PASSWORD: env.SAP_PASSWORD || env.SAP_PASS || ''
|
|
219
|
+
};
|
|
220
|
+
})();
|
|
205
221
|
return {
|
|
206
222
|
source: 'sap-gui-local',
|
|
207
223
|
name,
|
|
@@ -209,11 +225,8 @@ async function configuredLocalSapGuiDestination(env) {
|
|
|
209
225
|
backendUrl: tlsRoute ? env.SAP_URL : undefined,
|
|
210
226
|
client: env.SAP_CLIENT || '001',
|
|
211
227
|
systemId: env.SAP_SYSTEM_ID || '',
|
|
212
|
-
authentication: 'Basic',
|
|
213
|
-
childEnv
|
|
214
|
-
SAP_USER: env.SAP_USER || env.SAP_USERNAME || '',
|
|
215
|
-
SAP_PASSWORD: env.SAP_PASSWORD || env.SAP_PASS || ''
|
|
216
|
-
},
|
|
228
|
+
authentication: authMode === 'browser-saml' ? 'BrowserSAML' : authMode === 'saml-password' ? 'SAML' : 'Basic',
|
|
229
|
+
childEnv,
|
|
217
230
|
close: tlsRoute?.close,
|
|
218
231
|
probe: { status: tlsRoute ? 'configured-tls-server-name' : 'configured', available: true }
|
|
219
232
|
};
|
package/src/setup.mjs
CHANGED
|
@@ -61,7 +61,11 @@ async function chooseLocalAuthMode(destination, { input = stdin, output = stdout
|
|
|
61
61
|
defaultValue: 'basic',
|
|
62
62
|
choices: [
|
|
63
63
|
{ name: 'password', value: 'basic', shortcut: 'p' },
|
|
64
|
-
{ name: 'windows-sso (Kerberos/SPNEGO,
|
|
64
|
+
{ name: 'windows-sso (Kerberos/SPNEGO, silent current Windows logon)', value: 'windows-sso', shortcut: 's' },
|
|
65
|
+
{ name: 'windows-sso with native Windows credential UI / smartcard PIN', value: 'windows-credential-ui', shortcut: 'w' },
|
|
66
|
+
{ name: 'windows-sso with password fallback (self-heal when Negotiate/PIN is unavailable)', value: 'windows-sso-basic-fallback', shortcut: 'f' },
|
|
67
|
+
{ name: 'browser SAML / web SSO (opens browser if supported by VSP)', value: 'browser-saml', shortcut: 'b' },
|
|
68
|
+
{ name: 'SAML username/password', value: 'saml-password', shortcut: 'm' }
|
|
65
69
|
]
|
|
66
70
|
}, { input, output });
|
|
67
71
|
}
|
|
@@ -438,26 +442,56 @@ export async function runSetup({
|
|
|
438
442
|
}
|
|
439
443
|
}
|
|
440
444
|
const authMode = await chooseLocalAuthMode({ ...destination, name: promptLabel }, { input, output, env });
|
|
441
|
-
destination.authentication =
|
|
445
|
+
destination.authentication = ['windows-sso', 'windows-credential-ui', 'windows-sso-basic-fallback'].includes(authMode) ? 'WindowsSSO' : 'Basic';
|
|
442
446
|
destination.authMode = authMode;
|
|
443
447
|
for (const client of clients) {
|
|
444
448
|
const name = localClientDestinationName(destination, client, { multipleClients: clients.length > 1, duplicateSystemName });
|
|
445
449
|
const localDestination = { ...destination, name, serverName: name, client };
|
|
450
|
+
const userInputId = `sap-ai-dev-${localDestination.serverName || localDestination.name}-user`.toLowerCase().replace(/[^a-z0-9_-]+/g, '-');
|
|
451
|
+
const passwordInputId = `sap-ai-dev-${localDestination.serverName || localDestination.name}-password`.toLowerCase().replace(/[^a-z0-9_-]+/g, '-');
|
|
452
|
+
const credentialInputs = [
|
|
453
|
+
{ id: userInputId, type: 'promptString', description: `SAP user for ${localDestination.name}` },
|
|
454
|
+
{ id: passwordInputId, type: 'promptString', description: `SAP password for ${localDestination.name}`, password: true }
|
|
455
|
+
];
|
|
446
456
|
if (authMode === 'windows-sso') {
|
|
447
457
|
localDestination.childEnv = { SAP_AUTH_MODE: 'windows-sso' };
|
|
448
458
|
localDestination.inputs = [];
|
|
459
|
+
} else if (authMode === 'windows-credential-ui') {
|
|
460
|
+
localDestination.childEnv = {
|
|
461
|
+
SAP_AUTH_MODE: 'windows-sso',
|
|
462
|
+
SAP_AI_DEV_TOOLKIT_WINDOWS_CREDENTIAL_UI: 'true'
|
|
463
|
+
};
|
|
464
|
+
localDestination.inputs = [];
|
|
465
|
+
} else if (authMode === 'windows-sso-basic-fallback') {
|
|
466
|
+
localDestination.childEnv = {
|
|
467
|
+
SAP_AUTH_MODE: 'windows-sso',
|
|
468
|
+
SAP_AUTH_FALLBACK_MODE: 'basic',
|
|
469
|
+
SAP_USER: `\${input:${userInputId}}`,
|
|
470
|
+
SAP_PASSWORD: `\${input:${passwordInputId}}`
|
|
471
|
+
};
|
|
472
|
+
localDestination.inputs = credentialInputs;
|
|
473
|
+
} else if (authMode === 'browser-saml') {
|
|
474
|
+
localDestination.childEnv = {
|
|
475
|
+
SAP_AUTH_MODE: 'browser-saml',
|
|
476
|
+
SAP_BROWSER_AUTH: 'true',
|
|
477
|
+
SAP_SAML_AUTH: 'true'
|
|
478
|
+
};
|
|
479
|
+
localDestination.inputs = [];
|
|
480
|
+
} else if (authMode === 'saml-password') {
|
|
481
|
+
localDestination.childEnv = {
|
|
482
|
+
SAP_AUTH_MODE: 'saml-password',
|
|
483
|
+
SAP_SAML_AUTH: 'true',
|
|
484
|
+
SAP_SAML_USER: `\${input:${userInputId}}`,
|
|
485
|
+
SAP_SAML_PASSWORD: `\${input:${passwordInputId}}`
|
|
486
|
+
};
|
|
487
|
+
localDestination.inputs = credentialInputs;
|
|
449
488
|
} else {
|
|
450
|
-
const userInputId = `sap-ai-dev-${localDestination.serverName || localDestination.name}-user`.toLowerCase().replace(/[^a-z0-9_-]+/g, '-');
|
|
451
|
-
const passwordInputId = `sap-ai-dev-${localDestination.serverName || localDestination.name}-password`.toLowerCase().replace(/[^a-z0-9_-]+/g, '-');
|
|
452
489
|
localDestination.childEnv = {
|
|
453
490
|
SAP_AUTH_MODE: 'basic',
|
|
454
491
|
SAP_USER: `\${input:${userInputId}}`,
|
|
455
492
|
SAP_PASSWORD: `\${input:${passwordInputId}}`
|
|
456
493
|
};
|
|
457
|
-
localDestination.inputs =
|
|
458
|
-
{ id: userInputId, type: 'promptString', description: `SAP user for ${localDestination.name}` },
|
|
459
|
-
{ id: passwordInputId, type: 'promptString', description: `SAP password for ${localDestination.name}`, password: true }
|
|
460
|
-
];
|
|
494
|
+
localDestination.inputs = credentialInputs;
|
|
461
495
|
}
|
|
462
496
|
expanded.push(localDestination);
|
|
463
497
|
}
|
package/src/terminal-ui.mjs
CHANGED
|
@@ -186,11 +186,13 @@ export async function selectPrompt({ message, choices, defaultValue } = {}, { in
|
|
|
186
186
|
else if (key.name === 'return' || key.name === 'enter') return finish(choices[cursor]);
|
|
187
187
|
else if (chunk) {
|
|
188
188
|
const typed = String(chunk).toLowerCase();
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
189
|
+
let match = choices.findIndex(choice => !choice.disabled && String(choice.shortcut || '').toLowerCase() === typed);
|
|
190
|
+
if (match === -1) {
|
|
191
|
+
match = choices.findIndex(choice => !choice.disabled && (
|
|
192
|
+
String(choice.value || '').toLowerCase().startsWith(typed) ||
|
|
193
|
+
String(choice.name || '').toLowerCase().startsWith(typed)
|
|
194
|
+
));
|
|
195
|
+
}
|
|
194
196
|
if (match !== -1) cursor = match;
|
|
195
197
|
else return;
|
|
196
198
|
} else return;
|
|
@@ -21,9 +21,37 @@ function validateTarget(value) {
|
|
|
21
21
|
return url;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
function authChallengeValues(headers) {
|
|
25
|
+
const value = headers?.get?.('www-authenticate');
|
|
26
|
+
if (!value) return [];
|
|
27
|
+
const parts = [];
|
|
28
|
+
let start = 0;
|
|
29
|
+
let quoted = false;
|
|
30
|
+
let escaped = false;
|
|
31
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
32
|
+
const char = value[index];
|
|
33
|
+
if (escaped) {
|
|
34
|
+
escaped = false;
|
|
35
|
+
} else if (char === '\\' && quoted) {
|
|
36
|
+
escaped = true;
|
|
37
|
+
} else if (char === '"') {
|
|
38
|
+
quoted = !quoted;
|
|
39
|
+
} else if (char === ',' && !quoted) {
|
|
40
|
+
parts.push(value.slice(start, index).trim());
|
|
41
|
+
start = index + 1;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
parts.push(value.slice(start).trim());
|
|
45
|
+
return parts.filter(Boolean);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function hasAuthChallenge(headers, scheme) {
|
|
49
|
+
const pattern = new RegExp(`^${scheme}(?:\\s|$)`, 'i');
|
|
50
|
+
return authChallengeValues(headers).some(part => pattern.test(part));
|
|
51
|
+
}
|
|
52
|
+
|
|
24
53
|
function hasNegotiateChallenge(headers) {
|
|
25
|
-
|
|
26
|
-
return /(?:^|,)\s*Negotiate\b/i.test(value);
|
|
54
|
+
return hasAuthChallenge(headers, 'Negotiate');
|
|
27
55
|
}
|
|
28
56
|
|
|
29
57
|
function headerObject(headers) {
|
|
@@ -99,9 +127,10 @@ function helperCommand(env) {
|
|
|
99
127
|
const command = env.SAP_AI_DEV_TOOLKIT_NEGOTIATE_HELPER || env.SAP_AI_DEV_TOOLKIT_SSPI_HELPER;
|
|
100
128
|
if (command) return { command, args: [] };
|
|
101
129
|
if (process.platform !== 'win32') return null;
|
|
130
|
+
const interactiveCredentialUi = env.SAP_AI_DEV_TOOLKIT_WINDOWS_CREDENTIAL_UI === 'true' || env.SAP_WINDOWS_CREDENTIAL_UI === 'true';
|
|
102
131
|
return {
|
|
103
132
|
command: env.SystemRoot ? join(env.SystemRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe') : 'powershell.exe',
|
|
104
|
-
args: ['-NoLogo', '-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-File', join(root, 'scripts', 'windows-negotiate-helper.ps1')]
|
|
133
|
+
args: ['-NoLogo', '-NoProfile', ...(interactiveCredentialUi ? [] : ['-NonInteractive']), '-ExecutionPolicy', 'Bypass', '-File', join(root, 'scripts', 'windows-negotiate-helper.ps1')]
|
|
105
134
|
};
|
|
106
135
|
}
|
|
107
136
|
|
|
@@ -140,6 +169,28 @@ async function acquireNegotiateToken({ targetUrl, challenge, env, execFileImpl }
|
|
|
140
169
|
return execHelper(helper, { url: targetUrl, challenge, mechanism: 'Negotiate' }, execFileImpl, env);
|
|
141
170
|
}
|
|
142
171
|
|
|
172
|
+
function basicFallbackCredentials(env) {
|
|
173
|
+
const mode = String(env.SAP_AUTH_FALLBACK_MODE || env.SAP_AI_DEV_TOOLKIT_AUTH_FALLBACK_MODE || '').trim().toLowerCase();
|
|
174
|
+
const enabled = mode === 'basic' || String(env.SAP_AI_DEV_TOOLKIT_SSO_BASIC_FALLBACK || '').toLowerCase() === 'true';
|
|
175
|
+
const user = String(env.SAP_USER || env.SAP_USERNAME || '').trim();
|
|
176
|
+
const password = String(env.SAP_PASSWORD || env.SAP_PASS || '');
|
|
177
|
+
if (!enabled || !user || !password) return null;
|
|
178
|
+
return { user, password };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function hasBasicChallenge(headers) {
|
|
182
|
+
return hasAuthChallenge(headers, 'Basic');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function retryWithBasicFallback({ req, target, headers, body, fetchImpl, env }) {
|
|
186
|
+
const credentials = basicFallbackCredentials(env);
|
|
187
|
+
if (!credentials) return null;
|
|
188
|
+
return forward(req, target, {
|
|
189
|
+
...headers,
|
|
190
|
+
Authorization: `Basic ${Buffer.from(`${credentials.user}:${credentials.password}`).toString('base64')}`
|
|
191
|
+
}, body, fetchImpl);
|
|
192
|
+
}
|
|
193
|
+
|
|
143
194
|
async function forward(req, targetUrl, headers, body, fetchImpl) {
|
|
144
195
|
return fetchImpl(targetUrl, {
|
|
145
196
|
method: req.method,
|
|
@@ -181,15 +232,26 @@ export async function createWindowsSsoAdtProxy({ destinationUrl, env = process.e
|
|
|
181
232
|
let response = await forward(req, target, headers, body, fetchImpl);
|
|
182
233
|
if (response.status === 401 && hasNegotiateChallenge(response.headers)) {
|
|
183
234
|
const challenge = response.headers.get('www-authenticate') || '';
|
|
235
|
+
const cookies = mergeCookieHeader(headers.cookie || headers.Cookie, setCookies(response.headers));
|
|
184
236
|
const cancel = response.body?.cancel?.();
|
|
185
237
|
await cancel?.catch?.(() => {});
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
238
|
+
try {
|
|
239
|
+
const token = await acquireNegotiateToken({ targetUrl: target.href, challenge, env, execFileImpl });
|
|
240
|
+
const retryHeaders = { ...headers, Authorization: `Negotiate ${token}` };
|
|
241
|
+
delete retryHeaders.cookie;
|
|
242
|
+
delete retryHeaders.Cookie;
|
|
243
|
+
if (cookies) retryHeaders.Cookie = cookies;
|
|
244
|
+
response = await forward(req, target, retryHeaders, body, fetchImpl);
|
|
245
|
+
} catch (error) {
|
|
246
|
+
response = hasBasicChallenge(response.headers)
|
|
247
|
+
? await retryWithBasicFallback({ req, target, headers: { ...headers, ...(cookies ? { Cookie: cookies } : {}) }, body, fetchImpl, env })
|
|
248
|
+
: null;
|
|
249
|
+
if (!response) throw error;
|
|
250
|
+
}
|
|
251
|
+
} else if (response.status === 401 && hasBasicChallenge(response.headers) && basicFallbackCredentials(env)) {
|
|
252
|
+
const cancel = response.body?.cancel?.();
|
|
253
|
+
await cancel?.catch?.(() => {});
|
|
254
|
+
response = await retryWithBasicFallback({ req, target, headers, body, fetchImpl, env }) || response;
|
|
193
255
|
}
|
|
194
256
|
writeFetchResponse(res, response, base, proxyOrigin);
|
|
195
257
|
} catch (error) {
|