@jskit-ai/http-runtime 0.1.144 → 0.1.145
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/package.descriptor.mjs
CHANGED
package/package.json
CHANGED
|
@@ -288,8 +288,8 @@ function createHttpClient(options = {}) {
|
|
|
288
288
|
return normalizeResolvedRequestUrl(resolved, normalizedUrl);
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
-
async function maybeRetry({ response, method, state, data, stream }) {
|
|
292
|
-
if (!
|
|
291
|
+
async function maybeRetry({ response, method, state, data, stream, csrfEnabled = csrf.enabled }) {
|
|
292
|
+
if (!csrfEnabled) {
|
|
293
293
|
return false;
|
|
294
294
|
}
|
|
295
295
|
|
|
@@ -351,8 +351,10 @@ function createHttpClient(options = {}) {
|
|
|
351
351
|
const {
|
|
352
352
|
transport: _transport,
|
|
353
353
|
query: requestQuery,
|
|
354
|
+
csrf: requestCsrf,
|
|
354
355
|
...forwardedRequestOptions
|
|
355
356
|
} = requestOptions && typeof requestOptions === "object" ? requestOptions : {};
|
|
357
|
+
const csrfEnabled = csrf.enabled && requestCsrf !== false;
|
|
356
358
|
const requestUrl = appendRequestQueryToUrl(url, requestQuery, transport);
|
|
357
359
|
const resolvedRequestUrl = await resolveRequestUrl(requestUrl, {
|
|
358
360
|
originalUrl: url,
|
|
@@ -398,7 +400,7 @@ function createHttpClient(options = {}) {
|
|
|
398
400
|
config.body = JSON.stringify(config.body);
|
|
399
401
|
}
|
|
400
402
|
|
|
401
|
-
if (
|
|
403
|
+
if (csrfEnabled && unsafeMethods.has(method) && !hasHeader(headers, csrf.headerName)) {
|
|
402
404
|
const token = await ensureCsrfToken();
|
|
403
405
|
if (token) {
|
|
404
406
|
setHeaderIfMissing(headers, csrf.headerName, token);
|
|
@@ -409,6 +411,7 @@ function createHttpClient(options = {}) {
|
|
|
409
411
|
method,
|
|
410
412
|
config,
|
|
411
413
|
state: resolvedState,
|
|
414
|
+
csrfEnabled,
|
|
412
415
|
transport,
|
|
413
416
|
url: resolvedRequestUrl
|
|
414
417
|
};
|
|
@@ -445,6 +448,7 @@ function createHttpClient(options = {}) {
|
|
|
445
448
|
contentType,
|
|
446
449
|
isJson,
|
|
447
450
|
stream,
|
|
451
|
+
csrfEnabled,
|
|
448
452
|
retryRequest
|
|
449
453
|
}) {
|
|
450
454
|
if (
|
|
@@ -453,7 +457,8 @@ function createHttpClient(options = {}) {
|
|
|
453
457
|
method,
|
|
454
458
|
state,
|
|
455
459
|
data,
|
|
456
|
-
stream
|
|
460
|
+
stream,
|
|
461
|
+
csrfEnabled
|
|
457
462
|
})
|
|
458
463
|
) {
|
|
459
464
|
return retryRequest();
|
|
@@ -525,6 +530,7 @@ function createHttpClient(options = {}) {
|
|
|
525
530
|
contentType: result.contentType,
|
|
526
531
|
isJson: result.isJson,
|
|
527
532
|
stream,
|
|
533
|
+
csrfEnabled: requestContext.csrfEnabled,
|
|
528
534
|
retryRequest() {
|
|
529
535
|
return retryRequest(resolvedState);
|
|
530
536
|
}
|
package/test/client.test.js
CHANGED
|
@@ -56,6 +56,57 @@ test("request serializes json body and injects csrf token for unsafe methods", a
|
|
|
56
56
|
assert.equal(calls[1][1].body, JSON.stringify({ demo: true }));
|
|
57
57
|
});
|
|
58
58
|
|
|
59
|
+
test("request-level csrf false skips session preflight and is not forwarded to fetch", async (t) => {
|
|
60
|
+
for (const method of ["POST", "PATCH", "DELETE"]) {
|
|
61
|
+
await t.test(method, async () => {
|
|
62
|
+
const calls = [];
|
|
63
|
+
const client = createHttpClient({
|
|
64
|
+
fetchImpl: async (url, options) => {
|
|
65
|
+
calls.push([url, options]);
|
|
66
|
+
return mockResponse({ data: { ok: true } });
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
await client.request("/api/books/1", {
|
|
71
|
+
method,
|
|
72
|
+
csrf: false,
|
|
73
|
+
body: method === "DELETE" ? undefined : { title: "Book" }
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
assert.equal(calls.length, 1);
|
|
77
|
+
assert.equal(calls[0][0], "/api/books/1");
|
|
78
|
+
assert.equal(Object.hasOwn(calls[0][1], "csrf"), false);
|
|
79
|
+
assert.equal(Object.hasOwn(calls[0][1].headers, "csrf-token"), false);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("request-level csrf false does not retry a csrf-shaped failure", async () => {
|
|
85
|
+
const calls = [];
|
|
86
|
+
const client = createHttpClient({
|
|
87
|
+
fetchImpl: async (url, options) => {
|
|
88
|
+
calls.push([url, options]);
|
|
89
|
+
return mockResponse({
|
|
90
|
+
status: 403,
|
|
91
|
+
data: {
|
|
92
|
+
error: {
|
|
93
|
+
code: "CSRF_TOKEN_INVALID"
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
await assert.rejects(
|
|
101
|
+
client.request("/api/books/1", {
|
|
102
|
+
method: "DELETE",
|
|
103
|
+
csrf: false
|
|
104
|
+
})
|
|
105
|
+
);
|
|
106
|
+
assert.equal(calls.length, 1);
|
|
107
|
+
assert.equal(calls[0][0], "/api/books/1");
|
|
108
|
+
});
|
|
109
|
+
|
|
59
110
|
test("request resolves request urls after query encoding and before fetch", async () => {
|
|
60
111
|
const calls = [];
|
|
61
112
|
const contexts = [];
|