Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(parseIsomorphicRequest): bypassing cookies properly (#1155)
* fix(parseIsomorphicRequest): bypassing cookies properly * fix(parseIsomorphicRequest): add 'same-origin' as fallback value to credentials * chore(parseIsomorphicRequest): add coments and improve the test codes * fix(setRequestCookies): prevent overriding existing cookies Co-authored-by: Artem Zakharchenko <kettanaito@gmail.com>
- Loading branch information
1 parent
c3cd80a
commit 755bc9d
Showing
5 changed files
with
274 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
/** | ||
* @jest-environment jsdom | ||
*/ | ||
import { Headers } from 'headers-polyfill/lib' | ||
import { parseIsomorphicRequest } from './parseIsomorphicRequest' | ||
import { createIsomorphicRequest } from '../../../test/support/utils' | ||
import { MockedRequest } from '../../handlers/RequestHandler' | ||
|
||
test('parses an isomorphic request', () => { | ||
const request = parseIsomorphicRequest( | ||
createIsomorphicRequest({ | ||
id: 'request-1', | ||
method: 'POST', | ||
url: new URL('https://example.com/resource'), | ||
headers: new Headers({ | ||
'Content-Type': 'application/json', | ||
Cookie: 'token=abc-123', | ||
}), | ||
body: JSON.stringify({ | ||
id: 'user-1', | ||
}), | ||
}), | ||
) | ||
|
||
expect(request).toStrictEqual<MockedRequest>({ | ||
id: 'request-1', | ||
method: 'POST', | ||
url: new URL('https://example.com/resource'), | ||
headers: new Headers({ | ||
'Content-Type': 'application/json', | ||
Cookie: 'token=abc-123', | ||
}), | ||
cookies: { | ||
token: 'abc-123', | ||
}, | ||
destination: 'document', | ||
credentials: 'same-origin', | ||
referrer: '', | ||
referrerPolicy: 'no-referrer', | ||
mode: 'cors', | ||
keepalive: false, | ||
integrity: '', | ||
redirect: 'manual', | ||
cache: 'default', | ||
bodyUsed: false, | ||
body: { | ||
id: 'user-1', | ||
}, | ||
}) | ||
}) | ||
|
||
test('preserves cookies for request with "same-origin" credentials', () => { | ||
const request = parseIsomorphicRequest( | ||
createIsomorphicRequest({ | ||
credentials: 'same-origin', | ||
headers: new Headers({ | ||
Cookie: 'foo=bar', | ||
}), | ||
}), | ||
) | ||
|
||
expect(request).toMatchObject<Partial<MockedRequest>>({ | ||
credentials: 'same-origin', | ||
cookies: { foo: 'bar' }, | ||
}) | ||
}) | ||
|
||
test('preserves cookies for request with "include" credentials', () => { | ||
const request = parseIsomorphicRequest( | ||
createIsomorphicRequest({ | ||
credentials: 'include', | ||
headers: new Headers({ | ||
Cookie: 'foo=bar', | ||
}), | ||
}), | ||
) | ||
|
||
expect(request).toMatchObject<Partial<MockedRequest>>({ | ||
credentials: 'include', | ||
cookies: { foo: 'bar' }, | ||
}) | ||
}) | ||
|
||
test('sets request "credentials" to "same-origin" when no credentials were provided', () => { | ||
const request = parseIsomorphicRequest( | ||
createIsomorphicRequest({ | ||
credentials: undefined, | ||
}), | ||
) | ||
|
||
expect(request).toMatchObject<Partial<MockedRequest>>({ | ||
credentials: 'same-origin', | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
/** | ||
* @jest-environment jsdom | ||
*/ | ||
import { Headers } from 'headers-polyfill/lib' | ||
import { setRequestCookies } from './setRequestCookies' | ||
import { createMockedRequest } from '../../../test/support/utils' | ||
|
||
beforeAll(() => { | ||
document.cookie = '' | ||
}) | ||
|
||
afterEach(() => { | ||
document.cookie = '' | ||
}) | ||
|
||
test('preserves request cookies when there are no document cookies to infer', () => { | ||
const request = createMockedRequest({ | ||
headers: new Headers({ | ||
Cookie: 'token=abc-123', | ||
}), | ||
}) | ||
setRequestCookies(request) | ||
|
||
expect(request.cookies).toEqual({ | ||
token: 'abc-123', | ||
}) | ||
expect(request.headers.get('cookie')).toEqual('token=abc-123') | ||
}) | ||
|
||
test('infers document cookies for request with "same-origin" credentials', () => { | ||
// Emulate cookies already present on the document. | ||
document.cookie = 'documentCookie=yes' | ||
|
||
const request = createMockedRequest({ | ||
// Make the request to the same origin as the document location. | ||
url: new URL('/resource', location.href), | ||
headers: new Headers({ | ||
Cookie: 'token=abc-123', | ||
}), | ||
credentials: 'same-origin', | ||
}) | ||
setRequestCookies(request) | ||
|
||
expect(request.headers.get('cookie')).toEqual( | ||
'token=abc-123, documentCookie=yes', | ||
) | ||
expect(request.cookies).toEqual({ | ||
// Cookies present in the document must be forwarded. | ||
documentCookie: 'yes', | ||
token: 'abc-123', | ||
}) | ||
}) | ||
|
||
test('does not infer document cookies for request with "same-origin" credentials made to extraneous origin', () => { | ||
// Emulate cookies already present on the document. | ||
document.cookie = 'documentCookie=yes' | ||
|
||
const request = createMockedRequest({ | ||
// Make the request to a different origin as the document location. | ||
url: new URL('/resource', 'https://example.com'), | ||
headers: new Headers({ | ||
Cookie: 'token=abc-123', | ||
}), | ||
credentials: 'same-origin', | ||
}) | ||
setRequestCookies(request) | ||
|
||
expect(request.headers.get('cookie')).toEqual('token=abc-123') | ||
expect(request.cookies).toEqual({ | ||
token: 'abc-123', | ||
}) | ||
}) | ||
|
||
test('infers document cookies for request with "include" credentials', () => { | ||
// Emulate cookies already present on the document. | ||
document.cookie = 'documentCookie=yes' | ||
|
||
const request = createMockedRequest({ | ||
// Make the request to a different origin as the document location. | ||
url: new URL('/resource', location.href), | ||
headers: new Headers({ | ||
Cookie: 'token=abc-123', | ||
}), | ||
credentials: 'include', | ||
}) | ||
setRequestCookies(request) | ||
|
||
expect(request.headers.get('cookie')).toEqual( | ||
'token=abc-123, documentCookie=yes', | ||
) | ||
expect(request.cookies).toEqual({ | ||
// Cookies present in the document must be forwarded regardless. | ||
documentCookie: 'yes', | ||
token: 'abc-123', | ||
}) | ||
}) | ||
|
||
test('infers document cookies for request with "include" credentials made to extraneous origin', () => { | ||
// Emulate cookies already present on the document. | ||
document.cookie = 'documentCookie=yes' | ||
|
||
const request = createMockedRequest({ | ||
// Make the request to a different origin as the document location. | ||
url: new URL('/resource', 'https://example.com'), | ||
headers: new Headers({ | ||
Cookie: 'token=abc-123', | ||
}), | ||
credentials: 'include', | ||
}) | ||
setRequestCookies(request) | ||
|
||
expect(request.headers.get('cookie')).toEqual( | ||
'token=abc-123, documentCookie=yes', | ||
) | ||
expect(request.cookies).toEqual({ | ||
// Cookies present in the document must be forwarded regardless. | ||
documentCookie: 'yes', | ||
token: 'abc-123', | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,53 @@ | ||
import * as cookieUtils from 'cookie' | ||
import { store } from '@mswjs/cookies' | ||
import { MockedRequest } from '../../handlers/RequestHandler' | ||
import { getRequestCookies } from './getRequestCookies' | ||
|
||
export function setRequestCookies(request: MockedRequest) { | ||
/** | ||
* Sets relevant cookies on the request. | ||
* Request cookies are taken from the following sources: | ||
* - Immediate (own) request cookies (those in the "Cookie" request header); | ||
* - From the `document.cookie` based on the request's `credentials` value; | ||
* - From the internal cookie store that persists/hydrates cookies in Node.js | ||
*/ | ||
export function setRequestCookies(request: MockedRequest): void { | ||
// Set mocked request cookies from the `cookie` header of the original request. | ||
// No need to take `credentials` into account, because in Node.js requests are intercepted | ||
// _after_ they happen. Request issuer should have already taken care of sending relevant cookies. | ||
// Unlike browser, where interception is on the worker level, _before_ the request happens. | ||
const requestCookiesString = request.headers.get('cookie') | ||
|
||
store.hydrate() | ||
request.cookies = { | ||
...getRequestCookies(request), | ||
...Array.from( | ||
store.get({ ...request, url: request.url.toString() })?.entries(), | ||
).reduce( | ||
(cookies, [name, { value }]) => Object.assign(cookies, { [name]: value }), | ||
{}, | ||
), | ||
|
||
const cookiesFromStore = Array.from( | ||
store.get({ ...request, url: request.url.toString() })?.entries(), | ||
).reduce((cookies, [name, { value }]) => { | ||
return Object.assign(cookies, { [name.trim()]: value }) | ||
}, {}) | ||
|
||
const cookiesFromDocument = getRequestCookies(request) | ||
|
||
const forwardedCookies = { | ||
...cookiesFromDocument, | ||
...cookiesFromStore, | ||
} | ||
|
||
request.headers.set( | ||
'cookie', | ||
Object.entries(request.cookies) | ||
.map(([name, value]) => `${name}=${value}`) | ||
.join('; '), | ||
) | ||
// Ensure the persisted (document) cookies are propagated to the request. | ||
// Propagated the cookies persisted in the Cookuie Store to the request headers. | ||
// This forwards relevant request cookies based on the request's credentials. | ||
for (const [name, value] of Object.entries(forwardedCookies)) { | ||
request.headers.append('cookie', `${name}=${value}`) | ||
} | ||
|
||
const ownCookies = requestCookiesString | ||
? cookieUtils.parse(requestCookiesString) | ||
: {} | ||
|
||
console.log({ cookiesFromDocument, cookiesFromStore, ownCookies, request }) | ||
|
||
request.cookies = { | ||
...request.cookies, | ||
...forwardedCookies, | ||
...ownCookies, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters