mirror of
https://github.com/cypress-io/cypress.git
synced 2026-02-12 02:00:06 -06:00
fix: Make cross-origin document.cookie work (#22594)
This commit is contained in:
@@ -17,14 +17,14 @@ context('cy.origin aliasing', () => {
|
||||
|
||||
it('fails for dom elements outside origin', (done) => {
|
||||
cy.on('fail', (err) => {
|
||||
expect(err.message).to.equal('`cy.get()` could not find a registered alias for: `@welcome_button`.\nYou have not aliased anything yet.')
|
||||
expect(err.message).to.equal('`cy.get()` could not find a registered alias for: `@link`.\nYou have not aliased anything yet.')
|
||||
done()
|
||||
})
|
||||
|
||||
cy.get('[data-cy="welcome"]').as('welcome_button')
|
||||
cy.get('[data-cy="cross-origin-secondary-link"]').as('link')
|
||||
|
||||
cy.origin('http://foobar.com:3500', () => {
|
||||
cy.get('@welcome_button').click()
|
||||
cy.get('@link').click()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -34,6 +34,11 @@ describe('cy.origin - cookie login', () => {
|
||||
cy.get('h1').invoke('text').should('equal', 'No user found')
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
// makes it nice and readable even on a small screen with devtools open :)
|
||||
cy.viewport(300, 400)
|
||||
})
|
||||
|
||||
/****************************************************************************
|
||||
Cookie Login Flow
|
||||
- localhost/fixtures/primary-origin.html:
|
||||
@@ -107,17 +112,6 @@ describe('cy.origin - cookie login', () => {
|
||||
verifyLoggedIn(username)
|
||||
})
|
||||
|
||||
it('makes cross-origin cookies readable via document.cookie', () => {
|
||||
cy.visit('/fixtures/primary-origin.html')
|
||||
cy.get('[data-cy="cookie-login"]').click()
|
||||
cy.origin('http://foobar.com:3500', { args: { username } }, ({ username }) => {
|
||||
cy.get('[data-cy="username"]').type(username)
|
||||
cy.get('[data-cy="login"]').click()
|
||||
})
|
||||
|
||||
cy.document().its('cookie').should('include', `user=${username}`)
|
||||
})
|
||||
|
||||
it('handles browser-sent cookies being overridden by server-kept cookies', () => {
|
||||
cy.visit('https://localhost:3502/fixtures/primary-origin.html')
|
||||
cy.get('[data-cy="cookie-login-override"]').click()
|
||||
@@ -421,10 +415,10 @@ describe('cy.origin - cookie login', () => {
|
||||
username = getUsername()
|
||||
|
||||
cy.visit('/fixtures/primary-origin.html')
|
||||
cy.get('[data-cy="cookie-login"]').click()
|
||||
})
|
||||
|
||||
it('expired -> not logged in', () => {
|
||||
cy.get('[data-cy="cookie-login"]').click()
|
||||
cy.origin('http://foobar.com:3500', { args: { username } }, ({ username }) => {
|
||||
const expires = (new Date()).toUTCString()
|
||||
|
||||
@@ -437,6 +431,7 @@ describe('cy.origin - cookie login', () => {
|
||||
})
|
||||
|
||||
it('expired -> not accessible via cy.getCookie()', () => {
|
||||
cy.get('[data-cy="cookie-login"]').click()
|
||||
cy.origin('http://foobar.com:3500', { args: { username } }, ({ username }) => {
|
||||
const expires = (new Date()).toUTCString()
|
||||
|
||||
@@ -449,6 +444,7 @@ describe('cy.origin - cookie login', () => {
|
||||
})
|
||||
|
||||
it('expired -> not accessible via document.cookie', () => {
|
||||
cy.get('[data-cy="cookie-login-land-on-idp"]').click()
|
||||
cy.origin('http://foobar.com:3500', { args: { username } }, ({ username }) => {
|
||||
const expires = (new Date()).toUTCString()
|
||||
|
||||
@@ -457,7 +453,10 @@ describe('cy.origin - cookie login', () => {
|
||||
cy.get('[data-cy="login"]').click()
|
||||
})
|
||||
|
||||
cy.document().its('cookie').should('not.include', 'user=')
|
||||
cy.origin('http://idp.com:3500', () => {
|
||||
cy.clearCookie('user')
|
||||
cy.document().its('cookie').should('not.include', 'user=')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -468,10 +467,10 @@ describe('cy.origin - cookie login', () => {
|
||||
username = getUsername()
|
||||
|
||||
cy.visit('/fixtures/primary-origin.html')
|
||||
cy.get('[data-cy="cookie-login"]').click()
|
||||
})
|
||||
|
||||
it('past max-age -> not logged in', () => {
|
||||
cy.get('[data-cy="cookie-login"]').click()
|
||||
cy.origin('http://foobar.com:3500', { args: { username } }, ({ username }) => {
|
||||
cy.get('[data-cy="username"]').type(username)
|
||||
cy.get('[data-cy="localhostCookieProps"]').type('Max-Age=1')
|
||||
@@ -487,6 +486,7 @@ describe('cy.origin - cookie login', () => {
|
||||
// in Firefox. this issue doesn't seem to be specific to cross-origin tests,
|
||||
// as it happens even using cy.setCookie()
|
||||
it('past max-age -> not accessible via cy.getCookie()', { browser: '!firefox' }, () => {
|
||||
cy.get('[data-cy="cookie-login"]').click()
|
||||
cy.origin('http://foobar.com:3500', { args: { username } }, ({ username }) => {
|
||||
cy.get('[data-cy="username"]').type(username)
|
||||
cy.get('[data-cy="localhostCookieProps"]').type('Max-Age=1')
|
||||
@@ -499,18 +499,25 @@ describe('cy.origin - cookie login', () => {
|
||||
})
|
||||
|
||||
it('past max-age -> not accessible via document.cookie', () => {
|
||||
cy.get('[data-cy="cookie-login-land-on-idp"]').click()
|
||||
cy.origin('http://foobar.com:3500', { args: { username } }, ({ username }) => {
|
||||
cy.get('[data-cy="username"]').type(username)
|
||||
cy.get('[data-cy="localhostCookieProps"]').type('Max-Age=1')
|
||||
cy.get('[data-cy="login"]').click()
|
||||
})
|
||||
|
||||
cy.wait(1000) // give cookie time to expire
|
||||
cy.reload()
|
||||
cy.document().its('cookie').should('not.include', 'user=')
|
||||
cy.origin('http://idp.com:3500', () => {
|
||||
cy.wait(1000) // give cookie time to expire
|
||||
cy.reload()
|
||||
cy.document().its('cookie').should('not.include', 'user=')
|
||||
})
|
||||
})
|
||||
|
||||
describe('preference over Expires', () => {
|
||||
beforeEach(() => {
|
||||
cy.get('[data-cy="cookie-login"]').click()
|
||||
})
|
||||
|
||||
it('past Max-Age, before Expires -> not logged in', () => {
|
||||
const expires = dayjs().add(1, 'day').toDate().toUTCString()
|
||||
|
||||
@@ -642,4 +649,152 @@ describe('cy.origin - cookie login', () => {
|
||||
verifyIdpNotLoggedIn({ isHttps: true, cookieKey: '__Secure-user' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('document.cookie', () => {
|
||||
let username
|
||||
|
||||
beforeEach(() => {
|
||||
username = getUsername()
|
||||
|
||||
cy.visit('/fixtures/primary-origin.html')
|
||||
})
|
||||
|
||||
it('gets cookie set by http request', () => {
|
||||
cy.get('[data-cy="cookie-login-land-on-idp"]').click()
|
||||
cy.origin('http://foobar.com:3500', { args: { username } }, ({ username }) => {
|
||||
cy.get('[data-cy="username"]').type(username)
|
||||
cy.get('[data-cy="login"]').click()
|
||||
})
|
||||
|
||||
cy.origin('http://idp.com:3500', { args: { username } }, ({ username }) => {
|
||||
cy.document().its('cookie').should('include', `user=${username}`)
|
||||
})
|
||||
})
|
||||
|
||||
it('works when setting cookie', () => {
|
||||
cy.get('[data-cy="cross-origin-secondary-link"]').click()
|
||||
cy.origin('http://foobar.com:3500', () => {
|
||||
cy.document().then((doc) => {
|
||||
doc.cookie = 'key=value'
|
||||
})
|
||||
|
||||
cy.document().its('cookie').should('equal', 'key=value')
|
||||
})
|
||||
})
|
||||
|
||||
it('works when setting cookie with extra, benign parts', () => {
|
||||
cy.get('[data-cy="cross-origin-secondary-link"]').click()
|
||||
cy.origin('http://foobar.com:3500', () => {
|
||||
cy.document().then((doc) => {
|
||||
doc.cookie = 'key=value; wont=beset'
|
||||
})
|
||||
|
||||
cy.document().its('cookie').should('equal', 'key=value')
|
||||
})
|
||||
})
|
||||
|
||||
it('cookie properties are preserved when set via automation', () => {
|
||||
cy.get('[data-cy="cross-origin-secondary-link"]').click()
|
||||
cy.origin('http://foobar.com:3500', () => {
|
||||
cy.document().then((doc) => {
|
||||
doc.cookie = 'key=value; SameSite=Strict; Path=/foo'
|
||||
})
|
||||
|
||||
cy.getCookie('key').then((cookie) => {
|
||||
expect(Cypress._.omit(cookie, 'expiry')).to.deep.equal({
|
||||
domain: '.foobar.com',
|
||||
httpOnly: false,
|
||||
name: 'key',
|
||||
path: '/foo',
|
||||
sameSite: 'strict',
|
||||
secure: false,
|
||||
value: 'value',
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('does not set cookie when invalid', () => {
|
||||
cy.get('[data-cy="cross-origin-secondary-link"]').click()
|
||||
cy.origin('http://foobar.com:3500', () => {
|
||||
cy.document().then((doc) => {
|
||||
doc.cookie = '=value'
|
||||
})
|
||||
|
||||
cy.document().its('cookie').should('equal', '')
|
||||
})
|
||||
})
|
||||
|
||||
it('works when setting subsequent cookies', () => {
|
||||
cy.get('[data-cy="cross-origin-secondary-link"]').click()
|
||||
cy.origin('http://foobar.com:3500', () => {
|
||||
cy.document().then((doc) => {
|
||||
doc.cookie = 'key1=value1'
|
||||
})
|
||||
|
||||
cy.document().its('cookie').should('equal', 'key1=value1')
|
||||
cy.document().then((doc) => {
|
||||
doc.cookie = 'key2=value2'
|
||||
})
|
||||
|
||||
cy.document().its('cookie').should('equal', 'key2=value2; key1=value1')
|
||||
})
|
||||
})
|
||||
|
||||
it('makes cookie available to cy.getCookie()', () => {
|
||||
cy.get('[data-cy="cross-origin-secondary-link"]').click()
|
||||
cy.origin('http://foobar.com:3500', () => {
|
||||
cy.document().then((doc) => {
|
||||
doc.cookie = 'key=value'
|
||||
})
|
||||
|
||||
cy.getCookie('key').its('value').should('equal', 'value')
|
||||
})
|
||||
})
|
||||
|
||||
it('no longer returns cookie after cy.clearCookie()', () => {
|
||||
cy.get('[data-cy="cookie-login-land-on-idp"]').click()
|
||||
cy.origin('http://foobar.com:3500', { args: { username } }, ({ username }) => {
|
||||
cy.get('[data-cy="username"]').type(username)
|
||||
cy.get('[data-cy="login"]').click()
|
||||
})
|
||||
|
||||
cy.origin('http://idp.com:3500', () => {
|
||||
cy.clearCookie('user')
|
||||
cy.document().its('cookie').should('equal', '')
|
||||
})
|
||||
})
|
||||
|
||||
it('no longer returns cookie after cy.clearCookies()', () => {
|
||||
cy.get('[data-cy="cookie-login-land-on-idp"]').click()
|
||||
cy.origin('http://foobar.com:3500', { args: { username } }, ({ username }) => {
|
||||
cy.get('[data-cy="username"]').type(username)
|
||||
cy.get('[data-cy="login"]').click()
|
||||
})
|
||||
|
||||
cy.origin('http://idp.com:3500', () => {
|
||||
cy.clearCookies()
|
||||
cy.document().its('cookie').should('equal', '')
|
||||
})
|
||||
})
|
||||
|
||||
it('works when setting cookie in addition to cookie that already exists from http request', () => {
|
||||
cy.get('[data-cy="cookie-login-land-on-idp"]').click()
|
||||
cy.origin('http://foobar.com:3500', { args: { username } }, ({ username }) => {
|
||||
cy.get('[data-cy="username"]').type(username)
|
||||
cy.get('[data-cy="login"]').click()
|
||||
})
|
||||
|
||||
cy.origin('http://idp.com:3500', { args: { username } }, ({ username }) => {
|
||||
cy.document().then((doc) => {
|
||||
doc.cookie = 'key=value'
|
||||
})
|
||||
|
||||
// order of the cookies differs depending on browser, so just
|
||||
// ensure that each one is there
|
||||
cy.document().its('cookie').should('include', 'key=value')
|
||||
cy.document().its('cookie').should('include', `user=${username}`)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -18,14 +18,28 @@
|
||||
<li><a data-cy="cookie-login-subdomain">Login with Social (subdomain)</a></li>
|
||||
<li><a data-cy="cookie-login-alias">Login with Social (aliased localhost)</a></li>
|
||||
<li><a data-cy="cookie-login-override">Login with Social (cookie override)</a></li>
|
||||
<li><a data-cy="welcome" href="http://localhost:3500/welcome">Go to Welcome</a></li>
|
||||
<li><a data-cy="cookie-login-land-on-idp">Login with Social (lands on idp)</a></li>
|
||||
</ul>
|
||||
<script>
|
||||
document.querySelector('[data-cy=cookie-login]').href = `http://localhost:3500/prelogin?redirect=${encodeURIComponent('http://www.foobar.com:3500/fixtures/auth/cookie-login.html?redirect=http://localhost:3500/login')}`
|
||||
document.querySelector('[data-cy=cookie-login-https]').href = `https://localhost:3502/prelogin?redirect=${encodeURIComponent('https://www.foobar.com:3502/fixtures/auth/cookie-login.html?redirect=https://localhost:3502/login')}`
|
||||
document.querySelector('[data-cy=cookie-login-subdomain]').href = `http://localhost:3500/prelogin?redirect=${encodeURIComponent('http://www.foobar.com:3500/fixtures/auth/cookie-login.html?redirect=http://localhost:3500/login&subdomain=true')}`
|
||||
document.querySelector('[data-cy=cookie-login-alias]').href = `http://localhost:3500/prelogin?redirect=${encodeURIComponent('http://www.foobar.com:3500/fixtures/auth/cookie-login.html?redirect=http://localhost:3500/login&alias=true')}`
|
||||
document.querySelector('[data-cy=cookie-login-override]').href = `https://localhost:3502/prelogin?override=true&redirect=${encodeURIComponent('https://www.foobar.com:3502/fixtures/auth/cookie-login.html?redirect=https://localhost:3502/login')}`
|
||||
function setHref (dataCy, redirect2, primaryQueryAddition = '') {
|
||||
const isHttps = redirect2.startsWith('https')
|
||||
const url = isHttps ? 'https://localhost:3502' : 'http://localhost:3500'
|
||||
const redirect1Path = '/fixtures/auth/cookie-login.html'
|
||||
const redirect = encodeURIComponent(isHttps
|
||||
? `https://www.foobar.com:3502${redirect1Path}?redirect=${redirect2}`
|
||||
: `http://www.foobar.com:3500${redirect1Path}?redirect=${redirect2}`)
|
||||
|
||||
document.querySelector(`[data-cy="${dataCy}"]`).href = (
|
||||
`${url}/prelogin?redirect=${redirect}${primaryQueryAddition}`
|
||||
)
|
||||
}
|
||||
|
||||
setHref('cookie-login', 'http://localhost:3500/login')
|
||||
setHref('cookie-login-https', 'https://localhost:3502/login')
|
||||
setHref('cookie-login-subdomain', 'http://localhost:3500/login&subdomain=true')
|
||||
setHref('cookie-login-alias', 'http://localhost:3500/login&alias=true')
|
||||
setHref('cookie-login-override', 'https://localhost:3502/login', '&override=true')
|
||||
setHref('cookie-login-land-on-idp', 'http://www.idp.com:3500/welcome')
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import _ from 'lodash'
|
||||
import Cookies from 'js-cookie'
|
||||
import { CookieJar, toughCookieToAutomationCookie } from '@packages/server/lib/util/cookies'
|
||||
|
||||
import $errUtils from './error_utils'
|
||||
|
||||
@@ -149,6 +150,11 @@ export const $Cookies = (namespace, domain) => {
|
||||
return _.extend(defaults, obj)
|
||||
},
|
||||
|
||||
parse (cookieString: string) {
|
||||
return CookieJar.parse(cookieString)
|
||||
},
|
||||
|
||||
toughCookieToAutomationCookie,
|
||||
}
|
||||
|
||||
return API
|
||||
|
||||
@@ -20,7 +20,7 @@ import RequestMiddleware from './request-middleware'
|
||||
import ResponseMiddleware from './response-middleware'
|
||||
import { DeferredSourceMapCache } from '@packages/rewriter'
|
||||
import type { RemoteStates } from '@packages/server/lib/remote_states'
|
||||
import type { CookieJar } from '@packages/server/lib/cookie-jar'
|
||||
import type { CookieJar } from '@packages/server/lib/util/cookies'
|
||||
import type { Automation } from '@packages/server/lib/automation/automation'
|
||||
|
||||
function getRandomColorFn () {
|
||||
|
||||
@@ -2,8 +2,7 @@ import _ from 'lodash'
|
||||
import type Debug from 'debug'
|
||||
import { URL } from 'url'
|
||||
import { cors } from '@packages/network'
|
||||
import { Cookie, CookieJar } from '@packages/server/lib/cookie-jar'
|
||||
import type { AutomationCookie } from '@packages/server/lib/automation/cookies'
|
||||
import { AutomationCookie, Cookie, CookieJar, toughCookieToAutomationCookie } from '@packages/server/lib/util/cookies'
|
||||
|
||||
interface RequestDetails {
|
||||
url: string
|
||||
@@ -32,26 +31,6 @@ export const getSameSiteContext = (autUrl: string | undefined, requestUrl: strin
|
||||
return isAUTFrameRequest ? 'lax' : 'none'
|
||||
}
|
||||
|
||||
const sameSiteNoneRe = /; +samesite=(?:'none'|"none"|none)/i
|
||||
|
||||
export const parseCookie = (cookie: string) => {
|
||||
const toughCookie = CookieJar.parse(cookie)
|
||||
|
||||
if (!toughCookie) return
|
||||
|
||||
// fixes tough-cookie defaulting undefined/invalid SameSite to 'none'
|
||||
// https://github.com/salesforce/tough-cookie/issues/191
|
||||
const hasUnspecifiedSameSite = toughCookie.sameSite === 'none' && !sameSiteNoneRe.test(cookie)
|
||||
|
||||
// not all browsers currently default to lax, but they're heading in that
|
||||
// direction since it's now the standard, so this is more future-proof
|
||||
if (hasUnspecifiedSameSite) {
|
||||
toughCookie.sameSite = 'lax'
|
||||
}
|
||||
|
||||
return toughCookie
|
||||
}
|
||||
|
||||
const comparableCookieString = (toughCookie: Cookie): string => {
|
||||
return _(toughCookie)
|
||||
.pick('key', 'value', 'domain', 'path')
|
||||
@@ -71,22 +50,6 @@ const matchesPreviousCookie = (previousCookies: Cookie[], cookie: Cookie) => {
|
||||
})
|
||||
}
|
||||
|
||||
const toughCookieToAutomationCookie = (toughCookie: Cookie, defaultDomain: string): AutomationCookie => {
|
||||
const expiry = toughCookie.expiryTime()
|
||||
|
||||
return {
|
||||
domain: toughCookie.domain || defaultDomain,
|
||||
expiry: isFinite(expiry) ? expiry / 1000 : null,
|
||||
httpOnly: toughCookie.httpOnly,
|
||||
maxAge: toughCookie.maxAge,
|
||||
name: toughCookie.key,
|
||||
path: toughCookie.path,
|
||||
sameSite: toughCookie.sameSite === 'none' ? 'no_restriction' : toughCookie.sameSite,
|
||||
secure: toughCookie.secure,
|
||||
value: toughCookie.value,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility for dealing with cross-origin cookies
|
||||
* - Tracks which cookies were added to our server-side cookie jar during
|
||||
@@ -140,7 +103,7 @@ export class CookiesHelper {
|
||||
}
|
||||
|
||||
setCookie (cookie: string) {
|
||||
const toughCookie = parseCookie(cookie)
|
||||
const toughCookie = CookieJar.parse(cookie)
|
||||
|
||||
// don't set the cookie in our own cookie jar if the parsed cookie is
|
||||
// undefined (meaning it's invalid) or if the browser would not set it
|
||||
|
||||
82
packages/runner/injection/cookies.js
Normal file
82
packages/runner/injection/cookies.js
Normal file
@@ -0,0 +1,82 @@
|
||||
/* global document */
|
||||
|
||||
// document.cookie monkey-patching
|
||||
// -------------------------------
|
||||
// We monkey-patch document.cookie when in a cross-origin injection, because
|
||||
// document.cookie runs into cross-origin restrictions when the AUT is on
|
||||
// a different origin than top. The goal is to make it act like it would
|
||||
// if the user's app was run in top.
|
||||
//
|
||||
// The general strategy is:
|
||||
// - Keep the document.cookie value (`documentCookieValue`) available so
|
||||
// the document.cookie getter can synchronously return it.
|
||||
// - Optimistically update that value when document.cookie is set, so that
|
||||
// subsequent synchronous calls to get the value will work.
|
||||
// - On an interval, get the browser's cookies for the given domain, so that
|
||||
// updates to the cookie jar (via http requests, cy.setCookie, etc) are
|
||||
// reflected in the document.cookie value.
|
||||
export const patchDocumentCookie = (Cypress) => {
|
||||
const setAutomationCookie = (toughCookie) => {
|
||||
const { superDomain } = Cypress.Location.create(window.location.href)
|
||||
const automationCookie = Cypress.Cookies.toughCookieToAutomationCookie(toughCookie, superDomain)
|
||||
|
||||
Cypress.automation('set:cookie', automationCookie)
|
||||
.catch(() => {
|
||||
// unlikely there will be errors, but ignore them in any case, since
|
||||
// they're not user-actionable
|
||||
})
|
||||
}
|
||||
|
||||
let documentCookieValue = ''
|
||||
|
||||
Object.defineProperty(document, 'cookie', {
|
||||
get () {
|
||||
return documentCookieValue
|
||||
},
|
||||
|
||||
set (newValue) {
|
||||
const cookie = Cypress.Cookies.parse(newValue)
|
||||
|
||||
// If cookie is undefined, it was invalid and couldn't be parsed
|
||||
if (!cookie) return documentCookieValue
|
||||
|
||||
const cookieString = `${cookie.key}=${cookie.value}`
|
||||
|
||||
// New cookies get prepended to existing cookies
|
||||
documentCookieValue = documentCookieValue.length
|
||||
? `${cookieString}; ${documentCookieValue}`
|
||||
: cookieString
|
||||
|
||||
setAutomationCookie(cookie)
|
||||
|
||||
return documentCookieValue
|
||||
},
|
||||
})
|
||||
|
||||
// The interval value is arbitrary; it shouldn't be too often, but needs to
|
||||
// be fairly frequent so that the local value is kept as up-to-date as
|
||||
// possible. It's possible there could be a race condition where
|
||||
// document.cookie returns an out-of-date value, but there's not really a
|
||||
// way around that since it's a synchronous API and we can only get the
|
||||
// browser's true cookie values asynchronously.
|
||||
const intervalId = setInterval(async () => {
|
||||
const { superDomain: domain } = Cypress.Location.create(window.location.href)
|
||||
|
||||
try {
|
||||
const cookies = await Cypress.automation('get:cookies', { domain })
|
||||
const cookiesString = (cookies || []).map((c) => `${c.name}=${c.value}`).join('; ')
|
||||
|
||||
documentCookieValue = cookiesString
|
||||
} catch (err) {
|
||||
// unlikely there will be errors, but ignore them in any case, since
|
||||
// they're not user-actionable
|
||||
}
|
||||
}, 250)
|
||||
|
||||
const onUnload = () => {
|
||||
window.removeEventListener('unload', onUnload)
|
||||
clearInterval(intervalId)
|
||||
}
|
||||
|
||||
window.addEventListener('unload', onUnload)
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
import { createTimers } from './timers'
|
||||
import { patchDocumentCookie } from './cookies'
|
||||
|
||||
const findCypress = () => {
|
||||
for (let index = 0; index < window.parent.frames.length; index++) {
|
||||
@@ -40,6 +41,8 @@ const findCypress = () => {
|
||||
|
||||
const Cypress = findCypress()
|
||||
|
||||
patchDocumentCookie(Cypress)
|
||||
|
||||
// the timers are wrapped in the injection code similar to the primary origin
|
||||
const timers = createTimers()
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Cookies } from './cookies'
|
||||
import { Screenshot } from './screenshot'
|
||||
import type { BrowserPreRequest } from '@packages/proxy'
|
||||
import type { AutomationMiddleware, OnRequestEvent } from '@packages/types'
|
||||
import { cookieJar } from '../cookie-jar'
|
||||
import { cookieJar } from '../util/cookies'
|
||||
|
||||
export type OnBrowserPreRequest = (browserPreRequest: BrowserPreRequest) => void
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import * as errors from './errors'
|
||||
import preprocessor from './plugins/preprocessor'
|
||||
import runEvents from './plugins/run_events'
|
||||
import * as session from './session'
|
||||
import { cookieJar } from './cookie-jar'
|
||||
import { cookieJar } from './util/cookies'
|
||||
import { getSpecUrl } from './project_utils'
|
||||
import type { LaunchOpts, OpenProjectLaunchOptions, InitializeProjectOptions } from '@packages/types'
|
||||
import { DataContext, getCtx } from '@packages/data-context'
|
||||
|
||||
@@ -32,7 +32,7 @@ import { createRoutesCT } from './routes-ct'
|
||||
import type { FoundSpec } from '@packages/types'
|
||||
import type { Server as WebSocketServer } from 'ws'
|
||||
import { RemoteStates } from './remote_states'
|
||||
import { cookieJar } from './cookie-jar'
|
||||
import { cookieJar } from './util/cookies'
|
||||
import type { Automation } from './automation/automation'
|
||||
import type { AutomationCookie } from './automation/cookies'
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import { openFile, OpenFileDetails } from './util/file-opener'
|
||||
import open from './util/open'
|
||||
import type { DestroyableHttpServer } from './util/server_destroy'
|
||||
import * as session from './session'
|
||||
import { cookieJar } from './cookie-jar'
|
||||
import { cookieJar } from './util/cookies'
|
||||
// eslint-disable-next-line no-duplicate-imports
|
||||
import type { Socket } from '@packages/socket'
|
||||
import path from 'path'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Cookie, CookieJar as ToughCookieJar } from 'tough-cookie'
|
||||
import type { AutomationCookie } from '../automation/cookies'
|
||||
|
||||
export { Cookie }
|
||||
export { AutomationCookie, Cookie }
|
||||
|
||||
interface CookieData {
|
||||
name: string
|
||||
@@ -8,6 +9,24 @@ interface CookieData {
|
||||
path?: string
|
||||
}
|
||||
|
||||
export const toughCookieToAutomationCookie = (toughCookie: Cookie, defaultDomain: string): AutomationCookie => {
|
||||
const expiry = toughCookie.expiryTime()
|
||||
|
||||
return {
|
||||
domain: toughCookie.domain || defaultDomain,
|
||||
expiry: isFinite(expiry) ? expiry / 1000 : null,
|
||||
httpOnly: toughCookie.httpOnly,
|
||||
maxAge: toughCookie.maxAge,
|
||||
name: toughCookie.key,
|
||||
path: toughCookie.path,
|
||||
sameSite: toughCookie.sameSite === 'none' ? 'no_restriction' : toughCookie.sameSite,
|
||||
secure: toughCookie.secure,
|
||||
value: toughCookie.value,
|
||||
}
|
||||
}
|
||||
|
||||
const sameSiteNoneRe = /; +samesite=(?:'none'|"none"|none)/i
|
||||
|
||||
/**
|
||||
* An adapter for tough-cookie's CookieJar
|
||||
* Holds onto cookies captured via the proxy, so they can be applied to
|
||||
@@ -16,8 +35,22 @@ interface CookieData {
|
||||
export class CookieJar {
|
||||
_cookieJar: ToughCookieJar
|
||||
|
||||
static parse (cookie) {
|
||||
return Cookie.parse(cookie)
|
||||
static parse (cookie: string) {
|
||||
const toughCookie = Cookie.parse(cookie)
|
||||
|
||||
if (!toughCookie) return
|
||||
|
||||
// fixes tough-cookie defaulting undefined/invalid SameSite to 'none'
|
||||
// https://github.com/salesforce/tough-cookie/issues/191
|
||||
const hasUnspecifiedSameSite = toughCookie.sameSite === 'none' && !sameSiteNoneRe.test(cookie)
|
||||
|
||||
// not all browsers currently default to lax, but they're heading in that
|
||||
// direction since it's now the standard, so this is more future-proof
|
||||
if (hasUnspecifiedSameSite) {
|
||||
toughCookie.sameSite = 'lax'
|
||||
}
|
||||
|
||||
return toughCookie
|
||||
}
|
||||
|
||||
constructor () {
|
||||
Reference in New Issue
Block a user