Skip to content

Commit

Permalink
feat: add attach cookie logic to requests based on xhr/fetch requests
Browse files Browse the repository at this point in the history
  • Loading branch information
AtofStryker committed Sep 8, 2022
1 parent 09efa0c commit ab25529
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 13 deletions.
14 changes: 11 additions & 3 deletions packages/proxy/lib/http/request-middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
}

Expand All @@ -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()
Expand Down
59 changes: 49 additions & 10 deletions packages/proxy/test/unit/http/request-middleware.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -299,15 +335,15 @@ 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)

expect(ctx.req.headers['cookie']).to.equal('jar=cookie1; jar=cookie2; request=cookie')
})

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')

Expand All @@ -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')

Expand Down Expand Up @@ -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')

Expand All @@ -364,31 +400,34 @@ 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
}
}))

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<any>
}
})

Expand Down

0 comments on commit ab25529

Please sign in to comment.