| <!DOCTYPE html> | 
 | <meta charset="utf-8"> | 
 | <script src="/resources/testharness.js"></script> | 
 | <script src="/resources/testharnessreport.js"></script> | 
 | <script src="/common/get-host-info.sub.js"></script> | 
 | <script src="helper.js" type="module"></script> | 
 |  | 
 | <script type="module"> | 
 |   import { | 
 |     expireCookie, | 
 |     documentHasCookie, | 
 |     waitForCookie, | 
 |     addCookieAndSessionCleanup, | 
 |     setupShardedServerState, | 
 |     configureServer, | 
 |     crossSiteFetch | 
 |   } from "./helper.js"; | 
 |  | 
 |   promise_test(async t => { | 
 |     await setupShardedServerState({crossSite: true}); | 
 |     const expectedCookieAndValue = "auth_cookie=abcdef0123"; | 
 |     const expectedCookieAttributes = `Domain=${location.hostname};Path=/device-bound-session-credentials;SameSite=Lax`; | 
 |     const expectedCookieAndAttributes = `${expectedCookieAndValue};${expectedCookieAttributes}`; | 
 |     addCookieAndSessionCleanup(t); | 
 |  | 
 |     configureServer({ | 
 |       // Set cookie details in order to specify that we should only bind a SameSite=Lax cookie for this test. | 
 |       cookieDetails: [ | 
 |         { | 
 |           nameAndValue: expectedCookieAndValue, | 
 |           attributes: expectedCookieAttributes, | 
 |         } | 
 |       ], | 
 |       // Since registration happens from a third-party context, we need | 
 |       // a SameSite=None cookie to tell us that registration completed. | 
 |       registrationExtraCookies: [ | 
 |         { | 
 |           nameAndValue: "get_session_instructions=done", | 
 |           attributes: "SameSite=None;Secure", | 
 |         } | 
 |       ] | 
 |     }); | 
 |  | 
 |     // Prompt starting a session in a third-party context, and wait until registration completes. | 
 |     const loginStatus = await crossSiteFetch(get_host_info().HTTPS_NOTSAMESITE_ORIGIN, `${location.protocol}//${location.host}/device-bound-session-credentials/login.py`, {credentials: "include"}); | 
 |     assert_equals(loginStatus, 200); | 
 |     await waitForCookie("get_session_instructions=done", /*expectCookie=*/true); | 
 |  | 
 |     // Since all cookies in the session are first-party, registration | 
 |     // from a third-party context should fail. | 
 |     expireCookie(expectedCookieAndAttributes); | 
 |     assert_false(documentHasCookie(expectedCookieAndValue)); | 
 |     const authResponseAfterExpiry = await fetch('verify_authenticated.py'); | 
 |     assert_equals(authResponseAfterExpiry.status, 403); | 
 |     assert_false(documentHasCookie(expectedCookieAndValue)); | 
 |   }, "Registration of first-party session not allowed in third-party context"); | 
 |  | 
 |   promise_test(async t => { | 
 |     await setupShardedServerState({crossSite: true}); | 
 |     const expectedCookieAndValueSameSiteLax = "auth_cookie_lax=abcdef0123"; | 
 |     const expectedCookieAttributesSameSiteLax = `Domain=${location.hostname};Path=/device-bound-session-credentials;SameSite=Lax`; | 
 |     const expectedCookieAndAttributesSameSiteLax = `${expectedCookieAndValueSameSiteLax};${expectedCookieAttributesSameSiteLax}`; | 
 |  | 
 |     const expectedCookieAndValueSameSiteNone = "auth_cookie=abcdef0123"; | 
 |     const expectedCookieAttributesSameSiteNone = `Domain=${location.hostname};Path=/device-bound-session-credentials;SameSite=None;Secure`; | 
 |     const expectedCookieAndAttributesSameSiteNone = `${expectedCookieAndValueSameSiteNone};${expectedCookieAttributesSameSiteNone}`; | 
 |     addCookieAndSessionCleanup(t); | 
 |  | 
 |     configureServer({ | 
 |       // Configure the server to bind both a SameSite=Lax and SameSite=None cookie | 
 |       cookieDetails: [ | 
 |         { | 
 |           nameAndValue: expectedCookieAndValueSameSiteLax, | 
 |           attributes: expectedCookieAttributesSameSiteLax, | 
 |         }, | 
 |         { | 
 |           nameAndValue: expectedCookieAndValueSameSiteNone, | 
 |           attributes: expectedCookieAttributesSameSiteNone, | 
 |         } | 
 |       ], | 
 |       // Since registration happens from a third-party context, we need | 
 |       // a SameSite=None cookie to tell us that registration completed. | 
 |       registrationExtraCookies: [ | 
 |         { | 
 |           nameAndValue: "get_session_instructions=done", | 
 |           attributes: "SameSite=None;Secure", | 
 |         } | 
 |       ] | 
 |     }); | 
 |  | 
 |     // Prompt starting a session in a third-party context, and wait until registration completes. | 
 |     const loginStatus = await crossSiteFetch(get_host_info().HTTPS_NOTSAMESITE_ORIGIN, `${location.protocol}//${location.host}/device-bound-session-credentials/login.py`, {credentials: "include"}); | 
 |     assert_equals(loginStatus, 200); | 
 |     await waitForCookie("get_session_instructions=done", /*expectCookie=*/true); | 
 |     // Because registration is happenin from a third-party context, only the | 
 |     // SameSite=None cookie will be set. | 
 |     assert_false(documentHasCookie(expectedCookieAndValueSameSiteLax)); | 
 |     assert_true(documentHasCookie(expectedCookieAndValueSameSiteNone)); | 
 |  | 
 |     // Since one cookies in the session is third-party, registration | 
 |     // from a third-party context should succeed. | 
 |     expireCookie(expectedCookieAndAttributesSameSiteLax); | 
 |     expireCookie(expectedCookieAndAttributesSameSiteNone); | 
 |     assert_false(documentHasCookie(expectedCookieAndValueSameSiteLax)); | 
 |     assert_false(documentHasCookie(expectedCookieAndValueSameSiteNone)); | 
 |     const authResponseAfterExpiry = await fetch('verify_authenticated.py'); | 
 |     assert_equals(authResponseAfterExpiry.status, 200); | 
 |     // While the registration was from a third-party context, the | 
 |     // refresh triggered by fetching verify_authenticated.py is | 
 |     // happening in a first-party context. So we get both cookies from | 
 |     // the refresh. | 
 |     assert_true(documentHasCookie(expectedCookieAndValueSameSiteLax)); | 
 |     assert_true(documentHasCookie(expectedCookieAndValueSameSiteNone)); | 
 |   }, "Registration of session with third-party cookies allowed in third-party context"); | 
 |  | 
 |   promise_test(async t => { | 
 |     await setupShardedServerState({crossSite: true}); | 
 |     const expectedCookieAndValue = "auth_cookie=abcdef0123"; | 
 |     const expectedCookieAttributes = `Domain=${location.hostname};Path=/device-bound-session-credentials;SameSite=Lax`; | 
 |     const expectedCookieAndAttributes = `${expectedCookieAndValue};${expectedCookieAttributes}`; | 
 |     addCookieAndSessionCleanup(t); | 
 |  | 
 |     configureServer({ | 
 |       // Set cookie details in order to specify that we should only bind a SameSite=Lax cookie for this test. | 
 |       cookieDetails: [ | 
 |         { | 
 |           nameAndValue: expectedCookieAndValue, | 
 |           attributes: expectedCookieAttributes, | 
 |         }, | 
 |       ], | 
 |       earlyChallengeForNextRegisteredSession: "early_challenge", | 
 |     }); | 
 |  | 
 |     // Prompt starting a session in a first-party context, and wait until registration completes. | 
 |     const loginResponse = await fetch('login.py'); | 
 |     assert_equals(loginResponse.status, 200); | 
 |     await waitForCookie(expectedCookieAndValue, /*expectCookie=*/true); | 
 |  | 
 |     // Try to set the challenge from a third-party context | 
 |     const challengeStatus = await crossSiteFetch(get_host_info().HTTPS_NOTSAMESITE_ORIGIN, `${location.protocol}//${location.host}/device-bound-session-credentials/request_early_challenge.py`, {method: 'POST', body: JSON.stringify({ useSingleHeader: true}), credentials: "include"}); | 
 |     assert_equals(challengeStatus, 200); | 
 |  | 
 |     // Since all cookies in the session are first-party, our attempt to | 
 |     // set a challenge from a third-party context should fail. This | 
 |     // causes a challenge mismatch at registration time. | 
 |     expireCookie(expectedCookieAndAttributes); | 
 |     assert_false(documentHasCookie(expectedCookieAndValue)); | 
 |     const authResponseAfterExpiry = await fetch('verify_authenticated.py'); | 
 |     assert_equals(authResponseAfterExpiry.status, 403); | 
 |     assert_false(documentHasCookie(expectedCookieAndValue)); | 
 |   }, "Set challenge of first-party not allowed in third-party context"); | 
 |  | 
 |   promise_test(async t => { | 
 |     await setupShardedServerState({crossSite: true}); | 
 |     const expectedCookieAndValueSameSiteLax = "auth_cookie_lax=abcdef0123"; | 
 |     const expectedCookieAttributesSameSiteLax = `Domain=${location.hostname};Path=/device-bound-session-credentials;SameSite=Lax`; | 
 |     const expectedCookieAndAttributesSameSiteLax = `${expectedCookieAndValueSameSiteLax};${expectedCookieAttributesSameSiteLax}`; | 
 |  | 
 |     const expectedCookieAndValueSameSiteNone = "auth_cookie=abcdef0123"; | 
 |     const expectedCookieAttributesSameSiteNone = `Domain=${location.hostname};Path=/device-bound-session-credentials;SameSite=None;Secure`; | 
 |     const expectedCookieAndAttributesSameSiteNone = `${expectedCookieAndValueSameSiteNone};${expectedCookieAttributesSameSiteNone}`; | 
 |     addCookieAndSessionCleanup(t); | 
 |  | 
 |     configureServer({ | 
 |       // Configure the server to bind both a SameSite=Lax and SameSite=None cookie | 
 |       cookieDetails: [ | 
 |         { | 
 |           nameAndValue: expectedCookieAndValueSameSiteLax, | 
 |           attributes: expectedCookieAttributesSameSiteLax, | 
 |         }, | 
 |         { | 
 |           nameAndValue: expectedCookieAndValueSameSiteNone, | 
 |           attributes: expectedCookieAttributesSameSiteNone, | 
 |         } | 
 |       ], | 
 |       earlyChallengeForNextRegisteredSession: "early_challenge", | 
 |     }); | 
 |  | 
 |     // Prompt starting a session in a first-party context, and wait until registration completes. | 
 |     const loginResponse = await fetch('login.py'); | 
 |     assert_equals(loginResponse.status, 200); | 
 |     await waitForCookie(expectedCookieAndValueSameSiteNone, /*expectCookie=*/true); | 
 |  | 
 |     // Try to set the challenge from a third-party context | 
 |     const challengeStatus = await crossSiteFetch(get_host_info().HTTPS_NOTSAMESITE_ORIGIN, `${location.protocol}//${location.host}/device-bound-session-credentials/request_early_challenge.py`, {method: 'POST', body: JSON.stringify({ useSingleHeader: true}), credentials: "include"}); | 
 |     assert_equals(challengeStatus, 200); | 
 |  | 
 |     // Since one cookie in the session is third-party, our attempt to | 
 |     // set a challenge from a third-party context should succeed. | 
 |     expireCookie(expectedCookieAndAttributesSameSiteLax); | 
 |     expireCookie(expectedCookieAndAttributesSameSiteNone); | 
 |     assert_false(documentHasCookie(expectedCookieAndValueSameSiteLax)); | 
 |     assert_false(documentHasCookie(expectedCookieAndValueSameSiteNone)); | 
 |     const authResponseAfterExpiry = await fetch('verify_authenticated.py'); | 
 |     assert_equals(authResponseAfterExpiry.status, 200); | 
 |     // While the challenge was set in a third-party context, the refresh | 
 |     // triggered by fetching verify_authenticated.py is happening in a | 
 |     // first-party context. So we get both cookies from the refresh. | 
 |     assert_true(documentHasCookie(expectedCookieAndValueSameSiteLax)); | 
 |     assert_true(documentHasCookie(expectedCookieAndValueSameSiteNone)); | 
 |   }, "Set challenge of session with third-party cookies allowed in third-party context"); | 
 | </script> |