From ab25529bbaf4a9d8d0b1ed62efd81779745796ee Mon Sep 17 00:00:00 2001 From: Bill Glesias Date: Wed, 7 Sep 2022 14:33:44 -0400 Subject: [PATCH] feat: add attach cookie logic to requests based on xhr/fetch requests --- packages/proxy/lib/http/request-middleware.ts | 14 ++++- .../test/unit/http/request-middleware.spec.ts | 59 +++++++++++++++---- 2 files changed, 60 insertions(+), 13 deletions(-) diff --git a/packages/proxy/lib/http/request-middleware.ts b/packages/proxy/lib/http/request-middleware.ts index a6f7e4043d67..b984606f0579 100644 --- a/packages/proxy/lib/http/request-middleware.ts +++ b/packages/proxy/lib/http/request-middleware.ts @@ -2,7 +2,7 @@ import _ from 'lodash' import { blocked, cors } from '@packages/network' import { InterceptRequest } from '@packages/net-stubbing' import type { HttpMiddleware } from './' -import { getSameSiteContext, addCookieJarCookiesToRequest } from './util/cookies' +import { getSameSiteContext, addCookieJarCookiesToRequest, shouldAttachAndSetCookies } from './util/cookies' import { doesTopNeedToBeSimulated } from './util/top-simulation' // do not use a debug namespace in this file - use the per-request `this.debug` instead @@ -61,9 +61,16 @@ const ExtractRequestedWithAndCredentialsIfApplicable: RequestMiddleware = functi } const MaybeAttachCrossOriginCookies: RequestMiddleware = function () { + if (!this.config.experimentalSessionAndOrigin || !doesTopNeedToBeSimulated(this)) { + return this.next() + } + + // Top needs to be simulated since the AUT is in a cross origin state. Get the requestedWith and credentials and see what cookies need to be attached const currentAUTUrl = this.getAUTUrl() + const shouldCookiesBeAttachedToRequest = shouldAttachAndSetCookies(this.req.proxiedUrl, currentAUTUrl, this.req.requestedWith, this.req.credentialsLevel) - if (!this.config.experimentalSessionAndOrigin || !currentAUTUrl) { + this.debug(`should cookies be attached to request?: ${shouldCookiesBeAttachedToRequest}`) + if (!shouldCookiesBeAttachedToRequest) { return this.next() } @@ -79,7 +86,8 @@ const MaybeAttachCrossOriginCookies: RequestMiddleware = function () { this.debug('existing cookies on request from cookie jar: %s', applicableCookiesInCookieJar.join('; ')) this.debug('add cookies to request from header: %s', cookiesOnRequest.join('; ')) - this.req.headers['cookie'] = addCookieJarCookiesToRequest(applicableCookiesInCookieJar, cookiesOnRequest) + // if the cookie header is empty (i.e. ''), set it to undefined for expected behavior + this.req.headers['cookie'] = addCookieJarCookiesToRequest(applicableCookiesInCookieJar, cookiesOnRequest) || undefined this.debug('cookies being sent with request: %s', this.req.headers['cookie']) this.next() diff --git a/packages/proxy/test/unit/http/request-middleware.spec.ts b/packages/proxy/test/unit/http/request-middleware.spec.ts index 984dc5e0eaba..a612af81f30e 100644 --- a/packages/proxy/test/unit/http/request-middleware.spec.ts +++ b/packages/proxy/test/unit/http/request-middleware.spec.ts @@ -7,6 +7,7 @@ import { CypressIncomingRequest, CypressOutgoingResponse } from '../../../lib' import { HttpBuffer, HttpBuffers } from '../../../lib/http/util/buffers' import { RemoteStates } from '@packages/server/lib/remote_states' import { CookieJar } from '@packages/server/lib/util/cookies' +import { HttpMiddlewareThis } from '../../../lib/http' describe('http/request-middleware', () => { it('exports the members in the correct order', () => { @@ -243,9 +244,44 @@ describe('http/request-middleware', () => { expect(ctx.req.headers['cookie']).to.equal('request=cookie') }) - it('prepends cookie jar cookies to request', async () => { + it('is a noop if does not need to simulate top', async () => { const ctx = await getContext() + ctx.remoteStates.isPrimaryOrigin.returns(true), + + await testMiddleware([MaybeAttachCrossOriginCookies], ctx) + + expect(ctx.req.headers['cookie']).to.equal('request=cookie') + }) + + it('is a noop if cookies do NOT need to be attached to request', async () => { + const ctx = await getContext(['request=cookie'], ['jar=cookie'], 'http://foobar.com', 'http://app.foobar.com') + + ctx.req.requestedWith = 'fetch' + ctx.req.credentialsLevel = 'omit' + + await testMiddleware([MaybeAttachCrossOriginCookies], ctx) + + expect(ctx.req.headers['cookie']).to.equal('request=cookie') + }) + + it('sets the cookie header to undefined if no cookies exist on the request, none in the jar, but cookies should be attached', async () => { + const ctx = await getContext([], [], 'http://foobar.com', 'http://app.foobar.com') + + ctx.req.requestedWith = 'xhr' + ctx.req.credentialsLevel = true + + await testMiddleware([MaybeAttachCrossOriginCookies], ctx) + + expect(ctx.req.headers['cookie']).to.equal(undefined) + }) + + it('prepends cookie jar cookies to request', async () => { + const ctx = await getContext(['request=cookie'], ['jar=cookie'], 'http://foobar.com', 'http://app.foobar.com') + + ctx.req.requestedWith = 'fetch' + ctx.req.credentialsLevel = 'include' + await testMiddleware([MaybeAttachCrossOriginCookies], ctx) expect(ctx.req.headers['cookie']).to.equal('jar=cookie; request=cookie') @@ -299,7 +335,7 @@ describe('http/request-middleware', () => { describe('does not add request cookie to request if cookie exists in jar, and preserves duplicate cookies when same key/value if', () => { describe('subdomain and TLD', () => { it('matches hierarchy', async () => { - const ctx = await getContext(['jar=cookie', 'request=cookie'], ['jar=cookie1; Domain=app.foobar.com', 'jar=cookie2; Domain=foobar.com', 'jar=cookie3; Domain=exclude.foobar.com'], 'http://app.foobar.com/generic') + const ctx = await getContext(['jar=cookie', 'request=cookie'], ['jar=cookie1; Domain=app.foobar.com', 'jar=cookie2; Domain=foobar.com', 'jar=cookie3; Domain=exclude.foobar.com'], 'http://app.foobar.com/generic', 'http://app.foobar.com/generic') await testMiddleware([MaybeAttachCrossOriginCookies], ctx) @@ -307,7 +343,7 @@ describe('http/request-middleware', () => { }) it('matches hierarchy and gives order to the cookie that was created first', async () => { - const ctx = await getContext(['jar=cookie', 'request=cookie'], ['jar=cookie1; Domain=app.foobar.com;', 'jar=cookie2; Domain=.foobar.com;'], 'http://app.foobar.com/generic') + const ctx = await getContext(['jar=cookie', 'request=cookie'], ['jar=cookie1; Domain=app.foobar.com;', 'jar=cookie2; Domain=.foobar.com;'], 'http://app.foobar.com/generic', 'http://app.foobar.com/generic') const cookies = ctx.getCookieJar().getCookies('http://app.foobar.com/generic', 'strict') @@ -321,7 +357,7 @@ describe('http/request-middleware', () => { }) it('matches hierarchy and gives order to the cookie with the most specific path, regardless of creation time', async () => { - const ctx = await getContext(['jar=cookie', 'request=cookie'], ['jar=cookie1; Domain=app.foobar.com; Path=/generic', 'jar=cookie2; Domain=.foobar.com;'], 'http://app.foobar.com/generic') + const ctx = await getContext(['jar=cookie', 'request=cookie'], ['jar=cookie1; Domain=app.foobar.com; Path=/generic', 'jar=cookie2; Domain=.foobar.com;'], 'http://app.foobar.com/generic', 'http://app.foobar.com/generic') const cookies = ctx.getCookieJar().getCookies('http://app.foobar.com/generic', 'strict') @@ -349,7 +385,7 @@ describe('http/request-middleware', () => { 'jar=cookie9; Domain=exclude.foobar.com; Path=/generic/specific', ] - const ctx = await getContext(['request=cookie'], cookieJarCookies, 'http://app.foobar.com/generic/specific') + const ctx = await getContext(['request=cookie'], cookieJarCookies, 'http://app.foobar.com/generic/specific', 'http://app.foobar.com/generic/specific') const cookies = ctx.getCookieJar().getCookies('http://app.foobar.com/generic', 'strict') @@ -364,12 +400,12 @@ describe('http/request-middleware', () => { }) }) - async function getContext (requestCookieStrings = ['request=cookie'], cookieJarStrings = ['jar=cookie'], autAndRequestUrl = 'http://foobar.com') { + async function getContext (requestCookieStrings = ['request=cookie'], cookieJarStrings = ['jar=cookie'], autUrl = 'http://foobar.com', requestUrl = 'http://foobar.com') { const cookieJar = new CookieJar() await Promise.all(cookieJarStrings.map(async (cookieString) => { try { - await cookieJar._cookieJar.setCookie(cookieString, autAndRequestUrl) + await cookieJar._cookieJar.setCookie(cookieString, requestUrl) } catch (e) { // likely doesn't match the url policy, path, or is another type of cookie mismatch return @@ -377,18 +413,21 @@ describe('http/request-middleware', () => { })) return { - getAUTUrl: () => autAndRequestUrl, + getAUTUrl: () => autUrl, getCookieJar: () => cookieJar, + remoteStates: { + isPrimaryOrigin: sinon.stub().returns(false), + }, config: { experimentalSessionAndOrigin: true }, req: { - proxiedUrl: autAndRequestUrl, + proxiedUrl: requestUrl, isAUTFrame: true, headers: { cookie: requestCookieStrings.join('; '), }, }, - } + } as HttpMiddlewareThis } })