diff --git a/packages/koa-shopify-auth/CHANGELOG.md b/packages/koa-shopify-auth/CHANGELOG.md index 8ae15d4b4b..04ad07cef1 100644 --- a/packages/koa-shopify-auth/CHANGELOG.md +++ b/packages/koa-shopify-auth/CHANGELOG.md @@ -7,6 +7,10 @@ and adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [3.1.61] - 2020-05-01 + +- Fixes ITP 2.3 and Safari 13.1 enable cookies loop [1413](https://github.com/Shopify/quilt/pull/1413) + ## [3.1.56] - 2020-02-03 - Package now forces cookies.secure to be true [1255](https://github.com/Shopify/quilt/pull/1255) diff --git a/packages/koa-shopify-auth/src/auth/client/itp-helper.ts b/packages/koa-shopify-auth/src/auth/client/itp-helper.ts new file mode 100644 index 0000000000..9529820440 --- /dev/null +++ b/packages/koa-shopify-auth/src/auth/client/itp-helper.ts @@ -0,0 +1,43 @@ +// Copied from https://github.com/Shopify/shopify_app +const itpHelper = `(function() { + function ITPHelper(opts) { + this.itpContent = document.getElementById('TopLevelInteractionContent'); + this.itpAction = document.getElementById('TopLevelInteractionButton'); + this.redirectUrl = opts.redirectUrl; + } + + ITPHelper.prototype.redirect = function() { + sessionStorage.setItem('shopify.top_level_interaction', true); + window.location.href = this.redirectUrl; + } + + ITPHelper.prototype.userAgentIsAffected = function() { + return Boolean(document.hasStorageAccess); + } + + ITPHelper.prototype.canPartitionCookies = function() { + var versionRegEx = /Version\\/12\\.0\\.?\\d? Safari/; + return versionRegEx.test(navigator.userAgent); + } + + ITPHelper.prototype.setUpContent = function(onClick) { + this.itpContent.style.display = 'block'; + this.itpAction.addEventListener('click', this.redirect.bind(this)); + } + + ITPHelper.prototype.execute = function() { + if (!this.itpContent) { + return; + } + + if (this.userAgentIsAffected()) { + this.setUpContent(); + } else { + this.redirect(); + } + } + + this.ITPHelper = ITPHelper; + })(window);`; + +export default itpHelper; diff --git a/packages/koa-shopify-auth/src/auth/client/polaris-css.ts b/packages/koa-shopify-auth/src/auth/client/polaris-css.ts new file mode 100644 index 0000000000..446383a7fe --- /dev/null +++ b/packages/koa-shopify-auth/src/auth/client/polaris-css.ts @@ -0,0 +1,341 @@ +const polarisCss = `html, +body { + min-height: 100%; + height: 100%; + font-size: 1.5rem; + font-weight: 400; + line-height: 2rem; + text-transform: initial; + letter-spacing: initial; + font-weight: 400; + color: #212b36; + font-family: -apple-system, BlinkMacSystemFont, San Francisco, Roboto, + Segoe UI, Helvetica Neue, sans-serif; +} + +@media (min-width: 40em) { + html, + body { + font-size: 1.4rem; + } +} + +html { + position: relative; + font-size: 62.5%; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + text-size-adjust: 100%; + text-rendering: optimizeLegibility; +} + +body { + min-height: 100%; + margin: 0; + padding: 0; + background-color: #f4f6f8; +} + +*, +*::before, +*::after { + box-sizing: border-box; +} + +h1, +h2, +h3, +h4, +h5, +h6, +p { + margin: 0; + font-size: 1em; + font-weight: 400; +} + +#CookiePartitionPrompt, #RequestStorageAccess { + display: none; +} + +.Polaris-Page { + margin: 0 auto; + padding: 0; + max-width: 99.8rem; +} + +@media (min-width: 30.625em) { + .Polaris-Page { + padding: 0 2rem; + } +} +@media (min-width: 46.5em) { + .Polaris-Page { + padding: 0 3.2rem; + } +} + +.Polaris-Page__Content { + margin: 2rem 0; +} + +@media (min-width: 46.5em) { + .Polaris-Page__Content { + margin-top: 2rem; + } +} + +@media (min-width: 46.5em) { + .Polaris-Page { + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + } +} + +.Polaris-Layout { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-box-align: start; + -ms-flex-align: start; + align-items: flex-start; + margin-top: -2rem; + margin-left: -2rem; +} + +.Polaris-Layout__Section { + -webkit-box-flex: 2; + -ms-flex: 2 2 48rem; + flex: 2 2 48rem; + min-width: 51%; +} + +.Polaris-Layout__Section--fullWidth { + -webkit-box-flex: 1; + -ms-flex: 1 1 100%; + flex: 1 1 100%; +} + +.Polaris-Layout__Section { + max-width: calc(100% - 2rem); + margin-top: 2rem; + margin-left: 2rem; +} + +.Polaris-Stack { + margin-top: -1.6rem; + margin-left: -1.6rem; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-box-align: stretch; + -ms-flex-align: stretch; + align-items: stretch; +} + +.Polaris-Stack > .Polaris-Stack__Item { + margin-top: 1.6rem; + margin-left: 1.6rem; + max-width: calc(100% - 1.6rem); +} + +.Polaris-Stack__Item { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + min-width: 0; +} + +.Polaris-Heading { + font-size: 1.7rem; + font-weight: 600; + line-height: 2.4rem; + margin: 0; +} + +@media (min-width: 40em) { + .Polaris-Heading { + font-size: 1.6rem; + } +} + +.Polaris-Card { + overflow: hidden; + background-color: white; + box-shadow: 0 0 0 1px rgba(63, 63, 68, 0.05), + 0 1px 3px 0 rgba(63, 63, 68, 0.15); +} + +.Polaris-Card + .Polaris-Card { + margin-top: 2rem; +} + +@media (min-width: 30.625em) { + .Polaris-Card { + border-radius: 3px; + } +} + +.Polaris-Card__Header { + padding: 2rem 2rem 0; +} + +.Polaris-Card__Section { + padding: 2rem; +} + +.Polaris-Card__Section + .Polaris-Card__Section { + border-top: 1px solid #dfe3e8; +} + +.Polaris-Card__Section--subdued { + background-color: #f9fafb; +} + +.Polaris-Stack--distributionTrailing { + -webkit-box-pack: end; + -ms-flex-pack: end; + justify-content: flex-end; +} + +.Polaris-Stack--vertical { + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; +} + +.Polaris-Button { + fill: #637381; + position: relative; + display: -webkit-inline-box; + display: -ms-inline-flexbox; + display: inline-flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + min-height: 3.6rem; + min-width: 3.6rem; + margin: 0; + padding: 0.7rem 1.6rem; + background: linear-gradient(to bottom, white, #f9fafb); + border: 1px solid #c4cdd5; + box-shadow: 0 1px 0 0 rgba(22, 29, 37, 0.05); + border-radius: 3px; + line-height: 1; + color: #212b36; + text-align: center; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + text-decoration: none; + transition-property: background, border, box-shadow; + transition-duration: 200ms; + transition-timing-function: cubic-bezier(0.64, 0, 0.35, 1); +} + +.Polaris-Button:hover { + background: linear-gradient(to bottom, #f9fafb, #f4f6f8); + border-color: #c4cdd5; +} + +.Polaris-Button:focus { + border-color: #5c6ac4; + outline: 0; + box-shadow: 0 0 0 1px #5c6ac4; +} + +.Polaris-Button:active { + background: linear-gradient(to bottom, #f4f6f8, #f4f6f8); + border-color: #c4cdd5; + box-shadow: 0 0 0 0 transparent, inset 0 1px 1px 0 rgba(99, 115, 129, 0.1), + inset 0 1px 4px 0 rgba(99, 115, 129, 0.2); +} + +.Polaris-Button.Polaris-Button--disabled { + fill: #919eab; + transition: none; + background: linear-gradient(to bottom, #f4f6f8, #f4f6f8); + color: #919eab; +} + +.Polaris-Button__Content { + font-size: 1.5rem; + font-weight: 400; + line-height: 1.6rem; + text-transform: initial; + letter-spacing: initial; + position: relative; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + min-width: 1px; + min-height: 1px; +} + +@media (min-width: 40em) { + .Polaris-Button__Content { + font-size: 1.4rem; + } +} + +.Polaris-Button--primary { + background: linear-gradient(to bottom, #6371c7, #5563c1); + border-color: #3f4eae; + box-shadow: inset 0 1px 0 0 #6774c8, 0 1px 0 0 rgba(22, 29, 37, 0.05), + 0 0 0 0 transparent; + color: white; + fill: white; +} + +.Polaris-Button--primary:hover { + background: linear-gradient(to bottom, #5c6ac4, #4959bd); + border-color: #3f4eae; + color: white; + text-decoration: none; +} + +.Polaris-Button--primary:focus { + border-color: #202e78; + box-shadow: inset 0 1px 0 0 #6f7bcb, 0 1px 0 0 rgba(22, 29, 37, 0.05), + 0 0 0 1px #202e78; +} + +.Polaris-Button--primary:active { + background: linear-gradient(to bottom, #3f4eae, #3f4eae); + border-color: #38469b; + box-shadow: inset 0 0 0 0 transparent, 0 1px 0 0 rgba(22, 29, 37, 0.05), + 0 0 1px 0 #38469b; +} + +.Polaris-Button--primary.Polaris-Button--disabled { + fill: white; + background: linear-gradient(to bottom, #bac0e6, #bac0e6); + border-color: #a7aedf; + box-shadow: none; + color: white; +}`; + +export default polarisCss; diff --git a/packages/koa-shopify-auth/src/auth/client/request-storage-access.ts b/packages/koa-shopify-auth/src/auth/client/request-storage-access.ts new file mode 100644 index 0000000000..2e51b6eddf --- /dev/null +++ b/packages/koa-shopify-auth/src/auth/client/request-storage-access.ts @@ -0,0 +1,25 @@ +// Copied from https://github.com/Shopify/shopify_app +const requestStorageAccess = (shop: string) => { + return `(function() { + function redirect() { + var targetInfo = { + myshopifyUrl: "https://${shop}", + hasStorageAccessUrl: "/auth/inline?shop=${shop}", + doesNotHaveStorageAccessUrl: "/auth/enable_cookies?shop=${shop}", + appTargetUrl: "/?shop=${shop}" + } + + if (window.top == window.self) { + // If the current window is the 'parent', change the URL by setting location.href + window.top.location.href = targetInfo.hasStorageAccessUrl; + } else { + var storageAccessHelper = new StorageAccessHelper(targetInfo); + storageAccessHelper.execute(); + } + } + + document.addEventListener("DOMContentLoaded", redirect); + })();`; +}; + +export default requestStorageAccess; diff --git a/packages/koa-shopify-auth/src/auth/client/storage-access-helper.ts b/packages/koa-shopify-auth/src/auth/client/storage-access-helper.ts new file mode 100644 index 0000000000..96c30c32b4 --- /dev/null +++ b/packages/koa-shopify-auth/src/auth/client/storage-access-helper.ts @@ -0,0 +1,151 @@ +// Copied from https://github.com/Shopify/shopify_app +const storageAccessHelper = `(function() { + var ACCESS_GRANTED_STATUS = 'storage_access_granted'; + var ACCESS_DENIED_STATUS = 'storage_access_denied'; + + function StorageAccessHelper(redirectData) { + this.redirectData = redirectData; + } + + StorageAccessHelper.prototype.setNormalizedLink = function(storageAccessStatus) { + return storageAccessStatus === ACCESS_GRANTED_STATUS ? this.redirectData.hasStorageAccessUrl : this.redirectData.doesNotHaveStorageAccessUrl; + } + + StorageAccessHelper.prototype.redirectToAppTLD = function(storageAccessStatus) { + var normalizedLink = document.createElement('a'); + + normalizedLink.href = this.setNormalizedLink(storageAccessStatus); + + data = JSON.stringify({ + message: 'Shopify.API.remoteRedirect', + data: { + location: normalizedLink.href, + } + }); + window.parent.postMessage(data, this.redirectData.myshopifyUrl); + } + + StorageAccessHelper.prototype.redirectToAppsIndex = function() { + window.parent.location.href = this.redirectData.myshopifyUrl + '/admin/apps'; + } + + StorageAccessHelper.prototype.redirectToAppTargetUrl = function() { + window.location.href = this.redirectData.appTargetUrl; + } + + StorageAccessHelper.prototype.sameSiteNoneIncompatible = function(ua) { + return ua.includes("iPhone OS 12_") || ua.includes("iPad; CPU OS 12_") || //iOS 12 + (ua.includes("UCBrowser/") + ? this.isOlderUcBrowser(ua) //UC Browser < 12.13.2 + : (ua.includes("Chrome/5") || ua.includes("Chrome/6"))) || + ua.includes("Chromium/5") || ua.includes("Chromium/6") || + (ua.includes(" OS X 10_14_") && + ((ua.includes("Version/") && ua.includes("Safari")) || //Safari on MacOS 10.14 + ua.endsWith("(KHTML, like Gecko)"))); //Web view on MacOS 10.14 + } + + StorageAccessHelper.prototype.isOlderUcBrowser = function(ua) { + var match = ua.match(/UCBrowser\\/(\\d+)\\.(\\d+)\\.(\\d+)\\./); + if (!match) return false; + var major = parseInt(match[1]); + var minor = parseInt(match[2]); + var build = parseInt(match[3]); + if (major != 12) return major < 12; + if (minor != 13) return minor < 13; + return build < 2; + } + + StorageAccessHelper.prototype.setCookie = function(value) { + if(!this.sameSiteNoneIncompatible(navigator.userAgent)) { + value += '; secure; SameSite=None' + } + document.cookie = value; + } + + StorageAccessHelper.prototype.grantedStorageAccess = function() { + try { + sessionStorage.setItem('shopify.granted_storage_access', true); + this.setCookie('shopify.granted_storage_access=true'); + if (!document.cookie) { + throw 'Cannot set third-party cookie.' + } + this.redirectToAppTargetUrl(); + } catch (error) { + console.warn('Third party cookies may be blocked.', error); + this.redirectToAppTLD(ACCESS_DENIED_STATUS); + } + } + + StorageAccessHelper.prototype.handleRequestStorageAccess = function() { + return document.requestStorageAccess().then(this.grantedStorageAccess.bind(this), this.redirectToAppsIndex.bind(this, ACCESS_DENIED_STATUS)); + } + + StorageAccessHelper.prototype.setupRequestStorageAccess = function() { + var requestContent = document.getElementById('RequestStorageAccess'); + var requestButton = document.getElementById('TriggerAllowCookiesPrompt'); + + requestButton.addEventListener('click', this.handleRequestStorageAccess.bind(this)); + requestContent.style.display = 'block'; + } + + StorageAccessHelper.prototype.handleHasStorageAccess = function() { + if (sessionStorage.getItem('shopify.granted_storage_access')) { + // If app was classified by ITP and used Storage Access API to acquire access + this.redirectToAppTargetUrl(); + } else { + // If app has not been classified by ITP and still has storage access + this.redirectToAppTLD(ACCESS_GRANTED_STATUS); + } + } + + StorageAccessHelper.prototype.handleGetStorageAccess = function() { + if (sessionStorage.getItem('shopify.top_level_interaction')) { + // If merchant has been redirected to interact with TLD (requirement for prompting request to gain storage access) + this.setupRequestStorageAccess(); + } else { + // If merchant has not been redirected to interact with TLD (requirement for prompting request to gain storage access) + this.redirectToAppTLD(ACCESS_DENIED_STATUS); + } + } + + StorageAccessHelper.prototype.manageStorageAccess = function() { + return document.hasStorageAccess().then(function(hasAccess) { + if (hasAccess) { + this.handleHasStorageAccess(); + } else { + this.handleGetStorageAccess(); + } + }.bind(this)); + } + + StorageAccessHelper.prototype.execute = function() { + if (ITPHelper.prototype.userAgentIsAffected()) { + this.manageStorageAccess(); + } else { + this.grantedStorageAccess(); + } + } + + /* ITP 2.0 solution: handles cookie partitioning */ + StorageAccessHelper.prototype.setUpHelper = function() { + return new ITPHelper({redirectUrl: window.shopOrigin + "/admin/apps/" + window.apiKey + window.returnTo}); + } + + StorageAccessHelper.prototype.setCookieAndRedirect = function() { + this.setCookie('shopify.cookies_persist=true'); + var helper = this.setUpHelper(); + helper.redirect(); + } + + StorageAccessHelper.prototype.setUpCookiePartitioning = function() { + var itpContent = document.getElementById('CookiePartitionPrompt'); + itpContent.style.display = 'block'; + + // var button = document.getElementById('AcceptCookies'); + // button.addEventListener('click', this.setCookieAndRedirect.bind(this)); + } + + this.StorageAccessHelper = StorageAccessHelper; + })(window);`; + +export default storageAccessHelper; diff --git a/packages/koa-shopify-auth/src/auth/client/top-level-interaction.ts b/packages/koa-shopify-auth/src/auth/client/top-level-interaction.ts new file mode 100644 index 0000000000..69984cadd9 --- /dev/null +++ b/packages/koa-shopify-auth/src/auth/client/top-level-interaction.ts @@ -0,0 +1,16 @@ +// Copied from https://github.com/Shopify/shopify_app +const topLevelInteraction = (shop: string) => { + return `(function() { + function setUpTopLevelInteraction() { + var TopLevelInteraction = new ITPHelper({ + redirectUrl: "/auth?shop=${shop}", + }); + + TopLevelInteraction.execute(); + } + + document.addEventListener("DOMContentLoaded", setUpTopLevelInteraction); + })();`; +}; + +export default topLevelInteraction; diff --git a/packages/koa-shopify-auth/src/auth/create-enable-cookies-redirect.ts b/packages/koa-shopify-auth/src/auth/create-enable-cookies-redirect.ts deleted file mode 100644 index 372d5912d5..0000000000 --- a/packages/koa-shopify-auth/src/auth/create-enable-cookies-redirect.ts +++ /dev/null @@ -1,19 +0,0 @@ -import {Context} from 'koa'; - -import createTopLevelRedirect from './create-top-level-redirect'; -import getCookieOptions from './cookie-options'; - -import {TEST_COOKIE_NAME} from './index'; - -export default function createEnableCookiesRedirect( - apiKey: string, - path: string, -) { - const redirect = createTopLevelRedirect(apiKey, path); - - return function topLevelOAuthRedirect(ctx: Context) { - // This is to avoid a redirect loop if the app doesn't use verifyRequest or set the test cookie elsewhere. - ctx.cookies.set(TEST_COOKIE_NAME, '1', getCookieOptions(ctx)); - redirect(ctx); - }; -} diff --git a/packages/koa-shopify-auth/src/auth/create-enable-cookies.ts b/packages/koa-shopify-auth/src/auth/create-enable-cookies.ts index 945c88bd68..a21671d730 100644 --- a/packages/koa-shopify-auth/src/auth/create-enable-cookies.ts +++ b/packages/koa-shopify-auth/src/auth/create-enable-cookies.ts @@ -2,6 +2,9 @@ import {Context} from 'koa'; import {OAuthStartOptions} from '../types'; +import css from './client/polaris-css'; +import itpHelper from './client/itp-helper'; +import topLevelInteraction from './client/top-level-interaction'; import Error from './errors'; const HEADING = 'Enable cookies'; @@ -27,339 +30,7 @@ export default function createEnableCookies({apiKey}: OAuthStartOptions) { Redirecting… @@ -367,41 +38,13 @@ export default function createEnableCookies({apiKey}: OAuthStartOptions) { -
+
@@ -423,7 +66,7 @@ export default function createEnableCookies({apiKey}: OAuthStartOptions) {
-
diff --git a/packages/koa-shopify-auth/src/auth/create-request-storage-access.ts b/packages/koa-shopify-auth/src/auth/create-request-storage-access.ts new file mode 100644 index 0000000000..7c67b2389c --- /dev/null +++ b/packages/koa-shopify-auth/src/auth/create-request-storage-access.ts @@ -0,0 +1,82 @@ +import {Context} from 'koa'; + +import {OAuthStartOptions} from '../types'; + +import css from './client/polaris-css'; +import itpHelper from './client/itp-helper'; +import requestStorageAccess from './client/request-storage-access'; +import storageAccessHelper from './client/storage-access-helper'; +import Error from './errors'; + +const HEADING = 'This app needs access to your browser data'; +const BODY = + 'Your browser is blocking this app from accessing your data. To continue using this app, click Continue, then click Allow if the browser prompts you.'; +const ACTION = 'Continue'; + +export default function createRequestStorageAccess({ + apiKey, +}: OAuthStartOptions) { + return function requestStorage(ctx: Context) { + const {query} = ctx; + const {shop} = query; + + if (shop == null) { + ctx.throw(400, Error.ShopParamMissing); + return; + } + + ctx.body = ` + + + + + + + Redirecting… + + + + +
+
+
+
+
+
+
+
+
+

${HEADING}

+
+
+

${BODY}

+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+ +`; + }; +} diff --git a/packages/koa-shopify-auth/src/auth/index.ts b/packages/koa-shopify-auth/src/auth/index.ts index 527fb67616..a1d25ae80e 100644 --- a/packages/koa-shopify-auth/src/auth/index.ts +++ b/packages/koa-shopify-auth/src/auth/index.ts @@ -5,19 +5,25 @@ import {OAuthStartOptions, AccessMode, NextFunction} from '../types'; import createOAuthStart from './create-oauth-start'; import createOAuthCallback from './create-oauth-callback'; import createEnableCookies from './create-enable-cookies'; -import createEnableCookiesRedirect from './create-enable-cookies-redirect'; import createTopLevelOAuthRedirect from './create-top-level-oauth-redirect'; +import createRequestStorageAccess from './create-request-storage-access'; const DEFAULT_MYSHOPIFY_DOMAIN = 'myshopify.com'; const DEFAULT_ACCESS_MODE: AccessMode = 'online'; export const TOP_LEVEL_OAUTH_COOKIE_NAME = 'shopifyTopLevelOAuth'; export const TEST_COOKIE_NAME = 'shopifyTestCookie'; +export const GRANTED_STORAGE_ACCESS_COOKIE_NAME = + 'shopify.granted_storage_access'; function hasCookieAccess({cookies}: Context) { return Boolean(cookies.get(TEST_COOKIE_NAME)); } +function grantedStorageAccess({cookies}: Context) { + return Boolean(cookies.get(GRANTED_STORAGE_ACCESS_COOKIE_NAME)); +} + function shouldPerformInlineOAuth({cookies}: Context) { return Boolean(cookies.get(TOP_LEVEL_OAUTH_COOKIE_NAME)); } @@ -47,15 +53,17 @@ export default function createShopifyAuth(options: OAuthStartOptions) { const enableCookiesPath = `${oAuthStartPath}/enable_cookies`; const enableCookies = createEnableCookies(config); - const enableCookiesRedirect = createEnableCookiesRedirect( - config.apiKey, - enableCookiesPath, - ); + const requestStorageAccess = createRequestStorageAccess(config); return async function shopifyAuth(ctx: Context, next: NextFunction) { ctx.cookies.secure = true; - if (ctx.path === oAuthStartPath && !hasCookieAccess(ctx)) { - await enableCookiesRedirect(ctx); + + if ( + ctx.path === oAuthStartPath && + !hasCookieAccess(ctx) && + !grantedStorageAccess(ctx) + ) { + await requestStorageAccess(ctx); return; } diff --git a/packages/koa-shopify-auth/src/auth/test/enable-cookies-redirect.test.ts b/packages/koa-shopify-auth/src/auth/test/enable-cookies-redirect.test.ts deleted file mode 100644 index 6ff08dc1cb..0000000000 --- a/packages/koa-shopify-auth/src/auth/test/enable-cookies-redirect.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -import querystring from 'querystring'; - -import {createMockContext} from '@shopify/jest-koa-mocks'; - -import createEnableCookiesRedirect from '../create-enable-cookies-redirect'; -import createTopLevelRedirect from '../create-top-level-redirect'; - -const mockTopLevelRedirect = jest.fn(); -jest.mock('../create-top-level-redirect', () => - jest.fn(() => mockTopLevelRedirect), -); - -const query = querystring.stringify.bind(querystring); -const baseUrl = 'myapp.com/auth'; -const shop = 'shop1.myshopify.io'; -const path = '/auth/enable_cookies'; -const apiKey = 'somekey'; - -describe('CreateEnableCookiesRedirect', () => { - it('sets the test cookie', () => { - const enableCookiesRedirect = createEnableCookiesRedirect(apiKey, path); - const ctx = createMockContext({ - url: `https://${baseUrl}?${query({shop})}`, - }); - - enableCookiesRedirect(ctx); - - expect(ctx.cookies.set).toHaveBeenCalledWith('shopifyTestCookie', '1', {}); - }); - - it('sets up and calls the top level redirect', () => { - const enableCookiesRedirect = createEnableCookiesRedirect(apiKey, path); - const ctx = createMockContext({ - url: `https://${baseUrl}?${query({shop})}`, - }); - - enableCookiesRedirect(ctx); - - expect(createTopLevelRedirect).toHaveBeenCalledWith(apiKey, path); - expect(mockTopLevelRedirect).toHaveBeenCalledWith(ctx); - }); -}); diff --git a/packages/koa-shopify-auth/src/auth/test/enable-cookies.test.ts b/packages/koa-shopify-auth/src/auth/test/enable-cookies.test.ts index 0e6becb9df..604a6ef6a1 100644 --- a/packages/koa-shopify-auth/src/auth/test/enable-cookies.test.ts +++ b/packages/koa-shopify-auth/src/auth/test/enable-cookies.test.ts @@ -26,6 +26,6 @@ describe('CreateEnableCookies', () => { expect(ctx.body).toContain('CookiePartitionPrompt'); expect(ctx.body).toContain(baseConfig.apiKey); expect(ctx.body).toContain(shopOrigin); - expect(ctx.body).toContain(`window.location.href = "/auth?shop=${shop}"`); + expect(ctx.body).toContain(`redirectUrl: "/auth?shop=${shop}"`); }); }); diff --git a/packages/koa-shopify-auth/src/auth/test/index.test.ts b/packages/koa-shopify-auth/src/auth/test/index.test.ts index 68fd5c79e4..4ace23bb57 100644 --- a/packages/koa-shopify-auth/src/auth/test/index.test.ts +++ b/packages/koa-shopify-auth/src/auth/test/index.test.ts @@ -2,7 +2,7 @@ import {createMockContext} from '@shopify/jest-koa-mocks'; import createShopifyAuth from '../index'; import createTopLevelOAuthRedirect from '../create-top-level-oauth-redirect'; -import createEnableCookiesRedirect from '../create-enable-cookies-redirect'; +import createRequestStorageAccess from '../create-request-storage-access'; import {OAuthStartOptions} from '../../types'; const mockTopLevelOAuthRedirect = jest.fn(); @@ -10,9 +10,9 @@ jest.mock('../create-top-level-oauth-redirect', () => jest.fn(() => mockTopLevelOAuthRedirect), ); -const mockEnableCookiesRedirect = jest.fn(); -jest.mock('../create-enable-cookies-redirect', () => - jest.fn(() => mockEnableCookiesRedirect), +const mockRequestStorageAccess = jest.fn(); +jest.mock('../create-request-storage-access', () => () => + mockRequestStorageAccess, ); const mockOAuthStart = jest.fn(); @@ -38,7 +38,7 @@ function nextFunction() {} describe('Index', () => { describe('with the /auth path', () => { describe('with no test cookie', () => { - it('redirects to enable cookies', async () => { + it('redirects to request storage access', async () => { const shopifyAuth = createShopifyAuth(baseConfig); const ctx = createMockContext({ url: `https://${baseUrl}`, @@ -46,11 +46,25 @@ describe('Index', () => { await shopifyAuth(ctx, nextFunction); - expect(createEnableCookiesRedirect).toHaveBeenCalledWith( + expect(mockRequestStorageAccess).toHaveBeenCalledWith(ctx); + }); + }); + + describe('with no test cookie but a granted storage access cookie', () => { + it('redirects to /auth/inline at the top-level', async () => { + const shopifyAuth = createShopifyAuth(baseConfig); + const ctx = createMockContext({ + url: `https://${baseUrl}`, + cookies: {'shopify.granted_storage_access': '1'}, + }); + + await shopifyAuth(ctx, nextFunction); + + expect(createTopLevelOAuthRedirect).toHaveBeenCalledWith( 'myapikey', - '/auth/enable_cookies', + '/auth/inline', ); - expect(mockEnableCookiesRedirect).toHaveBeenCalledWith(ctx); + expect(mockTopLevelOAuthRedirect).toHaveBeenCalledWith(ctx); }); });