Skip to content

Commit 976dd9d

Browse files
authored
Merge pull request #5166 from Shopify/gg-query-password-setting
Replace isStorefrontPasswordProtected with API call
2 parents 4266f60 + 4510992 commit 976dd9d

File tree

10 files changed

+91
-91
lines changed

10 files changed

+91
-91
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@shopify/cli-kit': patch
3+
'@shopify/theme': patch
4+
'@shopify/app': patch
5+
---
6+
7+
Utilize Admin API to determine if a storefront is password protected

packages/app/src/cli/services/dev/processes/theme-app-extension.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export async function setupPreviewThemeAppExtensionsProcess(
5252
])
5353

5454
const storeFqdn = adminSession.storeFqdn
55-
const storefrontPassword = (await isStorefrontPasswordProtected(storeFqdn))
55+
const storefrontPassword = (await isStorefrontPasswordProtected(adminSession))
5656
? await ensureValidPassword('', storeFqdn)
5757
: undefined
5858

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/* eslint-disable @typescript-eslint/consistent-type-definitions */
2+
import * as Types from './types.js'
3+
4+
import {TypedDocumentNode as DocumentNode} from '@graphql-typed-document-node/core'
5+
6+
export type OnlineStorePasswordProtectionQueryVariables = Types.Exact<{[key: string]: never}>
7+
8+
export type OnlineStorePasswordProtectionQuery = {onlineStore: {passwordProtection: {enabled: boolean}}}
9+
10+
export const OnlineStorePasswordProtection = {
11+
kind: 'Document',
12+
definitions: [
13+
{
14+
kind: 'OperationDefinition',
15+
operation: 'query',
16+
name: {kind: 'Name', value: 'OnlineStorePasswordProtection'},
17+
selectionSet: {
18+
kind: 'SelectionSet',
19+
selections: [
20+
{
21+
kind: 'Field',
22+
name: {kind: 'Name', value: 'onlineStore'},
23+
selectionSet: {
24+
kind: 'SelectionSet',
25+
selections: [
26+
{
27+
kind: 'Field',
28+
name: {kind: 'Name', value: 'passwordProtection'},
29+
selectionSet: {
30+
kind: 'SelectionSet',
31+
selections: [
32+
{kind: 'Field', name: {kind: 'Name', value: 'enabled'}},
33+
{kind: 'Field', name: {kind: 'Name', value: '__typename'}},
34+
],
35+
},
36+
},
37+
{kind: 'Field', name: {kind: 'Name', value: '__typename'}},
38+
],
39+
},
40+
},
41+
],
42+
},
43+
},
44+
],
45+
} as unknown as DocumentNode<OnlineStorePasswordProtectionQuery, OnlineStorePasswordProtectionQueryVariables>

packages/cli-kit/src/cli/api/graphql/admin/generated/types.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ export type ThemeRole =
250250
* publishing are restricted until the merchant resolves the licensing issue.
251251
*/
252252
| 'LOCKED'
253-
/** TThe currently published theme. There can only be one main theme at any time. */
253+
/** The currently published theme. There can only be one main theme at any time. */
254254
| 'MAIN'
255255
/** The currently published theme that is only accessible to a mobile client. */
256256
| 'MOBILE'
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
query OnlineStorePasswordProtection {
2+
onlineStore {
3+
passwordProtection {
4+
enabled
5+
}
6+
}
7+
}

packages/cli-kit/src/public/node/themes/api.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
import {MetafieldDefinitionsByOwnerType} from '../../../cli/api/graphql/admin/generated/metafield_definitions_by_owner_type.js'
1919
import {GetThemes} from '../../../cli/api/graphql/admin/generated/get_themes.js'
2020
import {GetTheme} from '../../../cli/api/graphql/admin/generated/get_theme.js'
21+
import {OnlineStorePasswordProtection} from '../../../cli/api/graphql/admin/generated/online_store_password_protection.js'
2122
import {restRequest, RestResponse, adminRequestDoc} from '@shopify/cli-kit/node/api/admin'
2223
import {AdminSession} from '@shopify/cli-kit/node/session'
2324
import {AbortError} from '@shopify/cli-kit/node/error'
@@ -356,6 +357,17 @@ export async function metafieldDefinitionsByOwnerType(type: MetafieldOwnerType,
356357
}))
357358
}
358359

360+
export async function passwordProtected(session: AdminSession): Promise<boolean> {
361+
const {onlineStore} = await adminRequestDoc(OnlineStorePasswordProtection, session)
362+
if (!onlineStore) {
363+
unexpectedGraphQLError("Unable to get details about the storefront's password protection")
364+
}
365+
366+
const {passwordProtection} = onlineStore
367+
368+
return passwordProtection.enabled
369+
}
370+
359371
async function request<T>(
360372
method: string,
361373
path: string,

packages/theme/src/cli/services/console.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {consoleLog} from '@shopify/cli-kit/node/output'
99
export async function ensureReplEnv(adminSession: AdminSession, storePasswordFlag?: string) {
1010
const themeId = await findOrCreateReplTheme(adminSession)
1111

12-
const storePassword = (await isStorefrontPasswordProtected(adminSession.storeFqdn))
12+
const storePassword = (await isStorefrontPasswordProtected(adminSession))
1313
? await ensureValidPassword(storePasswordFlag, adminSession.storeFqdn)
1414
: undefined
1515

packages/theme/src/cli/services/dev.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,8 @@ export async function dev(options: DevOptions) {
4242
return
4343
}
4444

45-
const storefrontPasswordPromise = isStorefrontPasswordProtected(options.adminSession.storeFqdn).then(
46-
(needsPassword) =>
47-
needsPassword ? ensureValidPassword(options.storePassword, options.adminSession.storeFqdn) : undefined,
45+
const storefrontPasswordPromise = await isStorefrontPasswordProtected(options.adminSession).then((needsPassword) =>
46+
needsPassword ? ensureValidPassword(options.storePassword, options.adminSession.storeFqdn) : undefined,
4847
)
4948

5049
const localThemeExtensionFileSystem = emptyThemeExtFileSystem()

packages/theme/src/cli/utilities/theme-environment/storefront-session.test.ts

Lines changed: 11 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -7,96 +7,29 @@ import {
77
import {describe, expect, test, vi} from 'vitest'
88
import {fetch} from '@shopify/cli-kit/node/http'
99
import {AbortError} from '@shopify/cli-kit/node/error'
10+
import {passwordProtected} from '@shopify/cli-kit/node/themes/api'
11+
import {type AdminSession} from '@shopify/cli-kit/node/session'
1012

1113
vi.mock('@shopify/cli-kit/node/http')
14+
vi.mock('@shopify/cli-kit/node/themes/api')
1215

1316
describe('Storefront API', () => {
1417
describe('isStorefrontPasswordProtected', () => {
15-
test('returns true when the request is redirected to the password page', async () => {
16-
// Given
17-
vi.mocked(fetch).mockResolvedValue(response({status: 200, url: 'https://store.myshopify.com/password'}))
18-
19-
// When
20-
const isProtected = await isStorefrontPasswordProtected('store.myshopify.com')
21-
22-
// Then
23-
expect(isProtected).toBe(true)
24-
expect(fetch).toBeCalledWith('https://store.myshopify.com', {
25-
method: 'GET',
26-
})
27-
})
28-
29-
test('returns false when request is not redirected', async () => {
30-
// Given
31-
vi.mocked(fetch).mockResolvedValue(response({status: 200, url: 'https://store.myshopify.com'}))
32-
33-
// When
34-
const isProtected = await isStorefrontPasswordProtected('store.myshopify.com')
35-
36-
// Then
37-
expect(isProtected).toBe(false)
38-
expect(fetch).toBeCalledWith('https://store.myshopify.com', {
39-
method: 'GET',
40-
})
41-
})
42-
43-
test('returns false when store redirects to a different domain', async () => {
44-
// Given
45-
vi.mocked(fetch).mockResolvedValue(response({status: 200, url: 'https://store.myshopify.se'}))
46-
47-
// When
48-
const isProtected = await isStorefrontPasswordProtected('store.myshopify.com')
49-
50-
// Then
51-
expect(isProtected).toBe(false)
52-
})
18+
const adminSession: AdminSession = {
19+
storeFqdn: 'example-store.myshopify.com',
20+
token: '123456',
21+
}
5322

54-
test('returns false when store redirects to a different URI', async () => {
23+
test('makes an API call to check if the storefront is password protected', async () => {
5524
// Given
56-
vi.mocked(fetch).mockResolvedValue(response({status: 200, url: 'https://store.myshopify.com/random'}))
25+
vi.mocked(passwordProtected).mockResolvedValueOnce(true)
5726

5827
// When
59-
const isProtected = await isStorefrontPasswordProtected('store.myshopify.com')
60-
61-
// Then
62-
expect(isProtected).toBe(false)
63-
})
64-
65-
test('return true when store redirects to /<locale>/password', async () => {
66-
// Given
67-
vi.mocked(fetch).mockResolvedValue(response({status: 200, url: 'https://store.myshopify.com/fr-CA/password'}))
68-
69-
// When
70-
const isProtected = await isStorefrontPasswordProtected('store.myshopify.com')
28+
const isProtected = await isStorefrontPasswordProtected(adminSession)
7129

7230
// Then
7331
expect(isProtected).toBe(true)
74-
})
75-
76-
test('returns false if response is not a 302', async () => {
77-
// Given
78-
vi.mocked(fetch).mockResolvedValue(response({status: 200, url: 'https://store.myshopify.com/random'}))
79-
80-
// When
81-
const isProtected = await isStorefrontPasswordProtected('store.myshopify.com')
82-
83-
// Then
84-
expect(isProtected).toBe(false)
85-
})
86-
87-
test('ignores query params', async () => {
88-
// Given
89-
vi.mocked(fetch)
90-
.mockResolvedValueOnce(response({status: 200, url: 'https://store.myshopify.com/random?a=b'}))
91-
.mockResolvedValueOnce(response({status: 200, url: 'https://store.myshopify.com/password?a=b'}))
92-
93-
// When
94-
const redirectToRandomPath = await isStorefrontPasswordProtected('store.myshopify.com')
95-
const redirectToPasswordPath = await isStorefrontPasswordProtected('store.myshopify.com')
96-
97-
// Then
98-
expect(redirectToRandomPath).toBe(false)
99-
expect(redirectToPasswordPath).toBe(true)
32+
expect(passwordProtected).toHaveBeenCalledWith(adminSession)
10033
})
10134
})
10235

packages/theme/src/cli/utilities/theme-environment/storefront-session.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,13 @@ import {defaultHeaders} from './storefront-utils.js'
33
import {fetch} from '@shopify/cli-kit/node/http'
44
import {AbortError} from '@shopify/cli-kit/node/error'
55
import {outputDebug} from '@shopify/cli-kit/node/output'
6+
import {type AdminSession} from '@shopify/cli-kit/node/session'
7+
import {passwordProtected} from '@shopify/cli-kit/node/themes/api'
68

79
export class ShopifyEssentialError extends Error {}
810

9-
export async function isStorefrontPasswordProtected(storeURL: string): Promise<boolean> {
10-
const response = await fetch(prependHttps(storeURL), {
11-
method: 'GET',
12-
})
13-
14-
const redirectLocation = new URL(response.url)
15-
return redirectLocation.pathname.endsWith('/password')
11+
export async function isStorefrontPasswordProtected(session: AdminSession): Promise<boolean> {
12+
return passwordProtected(session)
1613
}
1714

1815
/**

0 commit comments

Comments
 (0)