diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ee330309f..945863fcee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,7 @@ - [Generator] Fix function imports (OData V2 + V4) and action imports, where the return type is a primitive edm type like `Edm.String`. - [Generator] Fix path references in generator configuration file to be treated as relative to the configuration file. - [Core] Fix expiration times in the client credentials cache. - +- [Core] Fix csrf request redirects against On-Premise systems # 1.30.0 diff --git a/packages/core/src/header-builder/csrf-token-header.ts b/packages/core/src/header-builder/csrf-token-header.ts index 3b429a4c23..1dd18af403 100644 --- a/packages/core/src/header-builder/csrf-token-header.ts +++ b/packages/core/src/header-builder/csrf-token-header.ts @@ -1,4 +1,5 @@ import { createLogger, errorWithCause } from '@sap-cloud-sdk/util'; +import { AxiosError } from 'axios'; import { HttpRequestConfig, executeHttpRequest } from '../http-client'; import { Destination, DestinationNameAndJwt } from '../scp-cf'; import { filterNullishValues, getHeader, getHeaderValue } from './header-util'; @@ -49,12 +50,33 @@ function makeCsrfRequest( .then(response => response.headers) .catch(error => { if (!error.response) { - throw errorWithCause('The error response is undefined.', error); + // TODO: remove once https://github.com/axios/axios/issues/3369 is fixed + const retry = axiosWorkaround(error, requestConfig, destination); + if (retry) { + return retry; + } + throw errorWithCause('Csrf fetch failed.', error); } return error.response.headers; }); } +function axiosWorkaround( + error: AxiosError, + axiosConfig: Partial, + destination: Destination | DestinationNameAndJwt +) { + if (error.request?._isRedirect && error.request?._options?.path) { + logger.warn( + 'Csrf fetch was redirected and failed. This might be a bug in the underlying request library (https://github.com/axios/axios/issues/3369).\nRetrying with full configuration.' + ); + return makeCsrfRequest(destination, { + ...axiosConfig, + url: error.request._options.path + }); + } +} + function validateCsrfTokenResponse(responseHeaders: Record) { if (!responseHeaders['x-csrf-token']) { logger.warn( diff --git a/packages/core/test/request-builder/header-builder/csrf-token-header.spec.ts b/packages/core/test/request-builder/header-builder/csrf-token-header.spec.ts index 8e20285c26..1cbb975460 100644 --- a/packages/core/test/request-builder/header-builder/csrf-token-header.spec.ts +++ b/packages/core/test/request-builder/header-builder/csrf-token-header.spec.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sap-cloud-sdk/util'; +import nock = require('nock'); import { buildCsrfHeaders } from '../../../src/header-builder'; import { createCreateRequest, @@ -8,9 +9,11 @@ import { import { defaultBasicCredentials, defaultDestination, + defaultHost, mockHeaderRequest } from '../../test-util/request-mocker'; import { addCsrfTokenAndCookies } from '../../../src/header-builder/legacy-csrf-token-header'; +import { Destination } from '../../../src'; const standardHeaders = { accept: 'application/json', @@ -146,4 +149,36 @@ describe('csrf-token-header', () => { expect('cookie' in actual).toBeFalsy(); }); }); + + it('should redirect csrf request when using proxy', async () => { + const destination: Destination = { + ...defaultDestination, + proxyType: 'OnPremise' + }; + const request = createCreateRequest(destination); + const mockedHeaders = { + 'x-csrf-token': 'mocked-x-csrf-token', + 'set-cookie': ['mocked-cookie-0;mocked-cookie-1', 'mocked-cookie-2'] + }; + + nock(defaultHost) + .get(request.serviceUrl()) + .reply(307, undefined, { + location: `${defaultHost}${request.serviceUrl()}/` + }); + + nock(defaultHost) + .get(request.serviceUrl() + '/') + .reply(200, undefined, mockedHeaders); + + const expected = { + cookie: 'mocked-cookie-0;mocked-cookie-2', + 'x-csrf-token': mockedHeaders['x-csrf-token'] + }; + const headers = await buildCsrfHeaders(request.destination!, { + headers: standardHeaders, + url: request.relativeServiceUrl() + }); + expect(headers).toEqual(expected); + }); });