@sap-cloud-sdk/http-client
Version:
SAP Cloud SDK for JavaScript http-client
135 lines • 5.22 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.csrf = csrf;
exports.buildCsrfFetchHeaders = buildCsrfFetchHeaders;
const util_1 = require("@sap-cloud-sdk/util");
const axios_1 = __importDefault(require("axios"));
const internal_1 = require("@sap-cloud-sdk/resilience/internal");
const logger = (0, util_1.createLogger)('csrf-middleware');
/**
* Middleware for fetching a CSRF token. This middleware is added to all request per default.
* Use the `fetchCsrfToken` option to disable it.
* @param options - Options like URL or method to configure the token fetching.
* @returns The middleware for fetching CSRF tokens.
*/
function csrf(options) {
return (middlewareOptions) => async (requestConfig) => {
if (!needsCsrfToken(requestConfig)) {
return middlewareOptions.fn(requestConfig);
}
const csrfToken = await makeCsrfRequests(requestConfig, {
...options,
...middlewareOptions
});
if (csrfToken?.cookie) {
csrfToken.cookie = requestConfig.headers?.cookie
? [requestConfig.headers?.cookie, csrfToken?.cookie].join(';')
: csrfToken?.cookie;
}
requestConfig.headers = { ...requestConfig.headers, ...csrfToken };
return middlewareOptions.fn(requestConfig);
};
}
function needsCsrfToken(requestConfig) {
if (requestConfig.method.toLowerCase() === 'get') {
logger.debug('Method is GET no CSRF token needed.');
return false;
}
if ((0, util_1.pickValueIgnoreCase)(requestConfig.headers, 'x-csrf-token')) {
logger.debug('CSRF token header was already provided. Existing token used.');
return false;
}
return true;
}
function appendSlash(requestConfig) {
if (!requestConfig.url) {
requestConfig.url = '/';
}
else if (!requestConfig.url.endsWith('/')) {
requestConfig.url = `${requestConfig.url}/`;
}
return requestConfig;
}
function removeSlash(requestConfig) {
if (requestConfig.url.endsWith('/')) {
requestConfig.url = (0, util_1.removeTrailingSlashes)(requestConfig.url);
}
return requestConfig;
}
function getCsrfToken(headers) {
return Object.values((0, util_1.pickIgnoreCase)(headers, 'x-csrf-token'))[0];
}
function getSetCookieHeader(headers) {
const cookies = Object.values((0, util_1.pickIgnoreCase)(headers, 'set-cookie'));
// On cookie format: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
return (0, util_1.flatten)(cookies)
.map((cookie) => cookie.split(';')[0])
.join(';');
}
/**
* @param headers - Request header information.
* @returns CSRF related headers.
* @internal
*/
function buildCsrfFetchHeaders(headers) {
const contentLengthHeaderKey = (0, util_1.first)(Object.keys((0, util_1.pickIgnoreCase)(headers, 'content-length'))) ||
'content-length';
return {
'x-csrf-token': 'Fetch',
...headers,
[contentLengthHeaderKey]: 0
};
}
async function makeCsrfRequest(requestConfig, options) {
try {
const response = await (0, internal_1.executeWithMiddleware)(options.middleware, {
fn: axios_1.default.request,
fnArgument: requestConfig,
context: options.context
});
return findCsrfHeader(response.headers);
}
catch (error) {
if (findCsrfHeader(error.response?.headers)) {
return findCsrfHeader(error.response?.headers);
}
logger.warn(new util_1.ErrorWithCause(`Failed to get CSRF token from URL: ${requestConfig.url}.`, error));
}
}
function findCsrfHeader(headers) {
if (!headers) {
return;
}
const csrfHeader = getCsrfToken(headers);
if (!csrfHeader) {
return;
}
const cookieHeader = getSetCookieHeader(headers)
? { cookie: getSetCookieHeader(headers) }
: {};
return { 'x-csrf-token': csrfHeader, ...cookieHeader };
}
async function makeCsrfRequests(requestConfig, options) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { data, params, parameterEncoder, ...requestConfigWithoutData } = requestConfig;
const axiosConfig = {
...requestConfigWithoutData,
method: options.method || 'head',
url: options.url || requestConfig.url,
headers: buildCsrfFetchHeaders(requestConfig.headers)
};
// If the user set the URL to fetch the token we only do
if (options.url) {
return makeCsrfRequest(axiosConfig, options);
}
// The S/4 does a redirect if the CSRF token is fetched in case the '/' is not in the URL.
// TODO: remove once https://github.com/axios/axios/issues/3369 is really fixed. Issue is closed but problem stays.
// We try first with slash and then without
/* eslint-disable no-return-await */
return ((await makeCsrfRequest(appendSlash(axiosConfig), options)) ??
(await makeCsrfRequest(removeSlash(axiosConfig), options)));
}
//# sourceMappingURL=csrf-token-middleware.js.map