@shopify/cli-kit 4.9.5 → 5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/api/graphql/admin/generated/metafield_definitions_by_owner_type.d.ts +5 -0
- package/dist/cli/api/graphql/admin/generated/metafield_definitions_by_owner_type.js +22 -0
- package/dist/cli/api/graphql/admin/generated/metafield_definitions_by_owner_type.js.map +1 -1
- package/dist/private/node/api/graphql.d.ts +2 -1
- package/dist/private/node/api/graphql.js +12 -1
- package/dist/private/node/api/graphql.js.map +1 -1
- package/dist/private/node/api.d.ts +2 -0
- package/dist/private/node/api.js +49 -22
- package/dist/private/node/api.js.map +1 -1
- package/dist/private/node/command-json-schema.js +1 -0
- package/dist/private/node/command-json-schema.js.map +1 -1
- package/dist/private/node/constants.d.ts +1 -0
- package/dist/private/node/constants.js +1 -0
- package/dist/private/node/constants.js.map +1 -1
- package/dist/private/node/dev-instance-output.d.ts +47 -0
- package/dist/private/node/dev-instance-output.js +143 -0
- package/dist/private/node/dev-instance-output.js.map +1 -0
- package/dist/private/node/json-error.d.ts +1 -15
- package/dist/private/node/json-error.js +4 -98
- package/dist/private/node/json-error.js.map +1 -1
- package/dist/private/node/output-context.d.ts +8 -0
- package/dist/private/node/output-context.js +10 -0
- package/dist/private/node/output-context.js.map +1 -0
- package/dist/private/node/output.test.js +4 -4
- package/dist/private/node/output.test.js.map +1 -1
- package/dist/private/node/session/automation-token.d.ts +50 -0
- package/dist/private/node/session/automation-token.js +77 -0
- package/dist/private/node/session/automation-token.js.map +1 -0
- package/dist/private/node/session/exchange.d.ts +5 -1
- package/dist/private/node/session/exchange.js +97 -7
- package/dist/private/node/session/exchange.js.map +1 -1
- package/dist/private/node/session/permanent-store-domain.d.ts +21 -0
- package/dist/private/node/session/permanent-store-domain.js +72 -0
- package/dist/private/node/session/permanent-store-domain.js.map +1 -0
- package/dist/private/node/session.d.ts +3 -2
- package/dist/private/node/session.js +12 -3
- package/dist/private/node/session.js.map +1 -1
- package/dist/private/node/themes/trust-challenge.d.ts +31 -0
- package/dist/private/node/themes/trust-challenge.js +143 -0
- package/dist/private/node/themes/trust-challenge.js.map +1 -0
- package/dist/private/node/ui/alert.js +1 -1
- package/dist/private/node/ui/alert.js.map +1 -1
- package/dist/private/node/ui/components/ConcurrentOutput.d.ts +2 -7
- package/dist/private/node/ui/components/ConcurrentOutput.js +4 -8
- package/dist/private/node/ui/components/ConcurrentOutput.js.map +1 -1
- package/dist/private/node/ui/components/LoadingBar.js +9 -15
- package/dist/private/node/ui/components/LoadingBar.js.map +1 -1
- package/dist/private/node/ui/components/LoadingBar.test.js +45 -107
- package/dist/private/node/ui/components/LoadingBar.test.js.map +1 -1
- package/dist/private/node/ui/components/LoadingIndicator.d.ts +7 -0
- package/dist/private/node/ui/components/LoadingIndicator.js +22 -0
- package/dist/private/node/ui/components/LoadingIndicator.js.map +1 -0
- package/dist/private/node/ui/components/Table/Table.d.ts +8 -0
- package/dist/private/node/ui/components/Table/Table.js +18 -2
- package/dist/private/node/ui/components/Table/Table.js.map +1 -1
- package/dist/public/common/version.d.ts +1 -1
- package/dist/public/common/version.js +1 -1
- package/dist/public/common/version.js.map +1 -1
- package/dist/public/node/api/admin.js +13 -0
- package/dist/public/node/api/admin.js.map +1 -1
- package/dist/public/node/api/graphql.js +38 -7
- package/dist/public/node/api/graphql.js.map +1 -1
- package/dist/public/node/archiver.js +13 -6
- package/dist/public/node/archiver.js.map +1 -1
- package/dist/public/node/base-command.d.ts +6 -0
- package/dist/public/node/base-command.js +97 -16
- package/dist/public/node/base-command.js.map +1 -1
- package/dist/public/node/dev-instance.d.ts +138 -0
- package/dist/public/node/dev-instance.js +365 -0
- package/dist/public/node/dev-instance.js.map +1 -0
- package/dist/public/node/environment.d.ts +6 -3
- package/dist/public/node/environment.js +11 -4
- package/dist/public/node/environment.js.map +1 -1
- package/dist/public/node/error/index.js +2 -1
- package/dist/public/node/error/index.js.map +1 -1
- package/dist/public/node/error/schema.d.ts +24 -24
- package/dist/public/node/error/serialization.d.ts +8 -0
- package/dist/public/node/error/serialization.js +103 -0
- package/dist/public/node/error/serialization.js.map +1 -0
- package/dist/public/node/graphiql/server.js +18 -2
- package/dist/public/node/graphiql/server.js.map +1 -1
- package/dist/public/node/is-global.js +2 -1
- package/dist/public/node/is-global.js.map +1 -1
- package/dist/public/node/notifications-system.d.ts +6 -6
- package/dist/public/node/path.d.ts +19 -3
- package/dist/public/node/path.js +21 -3
- package/dist/public/node/path.js.map +1 -1
- package/dist/public/node/session.d.ts +38 -3
- package/dist/public/node/session.js +56 -9
- package/dist/public/node/session.js.map +1 -1
- package/dist/public/node/system.d.ts +4 -0
- package/dist/public/node/system.js +39 -9
- package/dist/public/node/system.js.map +1 -1
- package/dist/public/node/themes/api.d.ts +9 -0
- package/dist/public/node/themes/api.js +50 -22
- package/dist/public/node/themes/api.js.map +1 -1
- package/dist/public/node/themes/theme-manager.d.ts +8 -1
- package/dist/public/node/themes/theme-manager.js +1 -1
- package/dist/public/node/themes/theme-manager.js.map +1 -1
- package/dist/public/node/tree-kill.js +12 -2
- package/dist/public/node/tree-kill.js.map +1 -1
- package/dist/public/node/ui/components.d.ts +1 -0
- package/dist/public/node/ui/components.js +1 -0
- package/dist/public/node/ui/components.js.map +1 -1
- package/dist/public/node/ui.d.ts +24 -4
- package/dist/public/node/ui.js +54 -21
- package/dist/public/node/ui.js.map +1 -1
- package/dist/public/node/upgrade/output.d.ts +17 -0
- package/dist/public/node/upgrade/output.js +51 -0
- package/dist/public/node/upgrade/output.js.map +1 -0
- package/dist/public/node/upgrade/result.d.ts +8 -0
- package/dist/public/node/upgrade/result.js +21 -0
- package/dist/public/node/upgrade/result.js.map +1 -0
- package/dist/public/node/upgrade/types.d.ts +61 -0
- package/dist/public/node/upgrade/types.js +46 -0
- package/dist/public/node/upgrade/types.js.map +1 -0
- package/dist/public/node/upgrade.d.ts +8 -0
- package/dist/public/node/upgrade.js +57 -22
- package/dist/public/node/upgrade.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/dist/private/node/ui/components/TextAnimation.d.ts +0 -10
- package/dist/private/node/ui/components/TextAnimation.js +0 -56
- package/dist/private/node/ui/components/TextAnimation.js.map +0 -1
- package/dist/private/node/ui/components/TextAnimation.test.d.ts +0 -1
- package/dist/private/node/ui/components/TextAnimation.test.js +0 -73
- package/dist/private/node/ui/components/TextAnimation.test.js.map +0 -1
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { applicationId, clientId as getIdentityClientId } from './identity.js';
|
|
2
2
|
import { tokenExchangeScopes } from './scopes.js';
|
|
3
|
+
import { automationTokenAuthMethod, automationTokenVariable } from './automation-token.js';
|
|
4
|
+
import { environmentVariables } from '../constants.js';
|
|
3
5
|
import { identityFqdn } from '../../../public/node/context/fqdn.js';
|
|
4
6
|
import { shopifyFetch } from '../../../public/node/http.js';
|
|
5
7
|
import { err, ok } from '../../../public/node/result.js';
|
|
@@ -11,9 +13,96 @@ import * as jose from 'jose';
|
|
|
11
13
|
export class InvalidGrantError extends Error {
|
|
12
14
|
}
|
|
13
15
|
export class InvalidRequestError extends Error {
|
|
16
|
+
/** Identity's `error_description`, when it sent one. Says what about the request was invalid. */
|
|
17
|
+
description;
|
|
18
|
+
constructor(description = '') {
|
|
19
|
+
super();
|
|
20
|
+
this.description = description;
|
|
21
|
+
}
|
|
14
22
|
}
|
|
15
23
|
class InvalidTargetError extends AbortError {
|
|
16
24
|
}
|
|
25
|
+
function automationTokenExchangeFailure(error) {
|
|
26
|
+
if (!(error instanceof InvalidRequestError))
|
|
27
|
+
return 'unknown';
|
|
28
|
+
// Identity's two ways of saying the token holds no scope the exchange asked for. Its third 'scope' message,
|
|
29
|
+
// "specified scopes cannot be granted to this audience", is about the CLI's own request, so it stays generic.
|
|
30
|
+
if (error.description.includes("Invalid 'scope' value: new scopes must not be specified") ||
|
|
31
|
+
error.description.includes("Invalid 'scope' value: no useable scope")) {
|
|
32
|
+
return 'missing_access';
|
|
33
|
+
}
|
|
34
|
+
if (error.description.includes("Invalid 'subject_token' value"))
|
|
35
|
+
return 'rejected_token';
|
|
36
|
+
return 'unknown';
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The capability a service account needs for the scopes an exchange asks for, named as the Shopify admin names it.
|
|
40
|
+
*/
|
|
41
|
+
function capabilityForScopes(scopes) {
|
|
42
|
+
if (scopes.includes('https://api.shopify.com/auth/organization.apps.manage'))
|
|
43
|
+
return 'App development';
|
|
44
|
+
if (scopes.includes('https://api.shopify.com/auth/organization.store-management'))
|
|
45
|
+
return 'Dev store management';
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The error shown when an automation token can't be exchanged for an API's access token.
|
|
50
|
+
*
|
|
51
|
+
* Says what Identity refused, so a token that merely lacks a capability isn't reported as wrong or expired.
|
|
52
|
+
*/
|
|
53
|
+
class AutomationTokenExchangeError extends AbortError {
|
|
54
|
+
constructor(apiName, scopes, cause) {
|
|
55
|
+
const { message, tryMessage } = automationTokenExchangeMessages(apiName, scopes, cause);
|
|
56
|
+
super(message, tryMessage);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function automationTokenExchangeMessages(apiName, scopes, cause) {
|
|
60
|
+
const prettyName = apiName.replace(/-/g, ' ').replace(/\b\w/g, (char) => char.toUpperCase());
|
|
61
|
+
const variable = automationTokenVariable() ?? environmentVariables.serviceAccountToken;
|
|
62
|
+
const admin = ['in the Shopify admin under', { subdued: 'Settings > Users > Security' }];
|
|
63
|
+
switch (automationTokenExchangeFailure(cause)) {
|
|
64
|
+
case 'missing_access': {
|
|
65
|
+
const capability = capabilityForScopes(scopes);
|
|
66
|
+
const access = capability ? `${capability} access` : `the access the ${prettyName} API needs`;
|
|
67
|
+
if (variable === environmentVariables.serviceAccountToken) {
|
|
68
|
+
return {
|
|
69
|
+
message: `The service account behind ${variable} doesn't have ${access}.`,
|
|
70
|
+
tryMessage: [
|
|
71
|
+
'Give it',
|
|
72
|
+
...(capability ? [{ bold: capability }, 'access'] : ['that access']),
|
|
73
|
+
...admin,
|
|
74
|
+
{ char: ',' },
|
|
75
|
+
'or use a token from a service account that has it.',
|
|
76
|
+
],
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
// Setting the new variable beside the old one is refused as a conflict, so say to unset it too.
|
|
80
|
+
return {
|
|
81
|
+
message: `The token in ${variable} doesn't have ${access}.`,
|
|
82
|
+
tryMessage: [
|
|
83
|
+
'Use a service account token that has it: create one',
|
|
84
|
+
...admin,
|
|
85
|
+
{ char: ',' },
|
|
86
|
+
'set it in',
|
|
87
|
+
{ command: environmentVariables.serviceAccountToken },
|
|
88
|
+
'and unset',
|
|
89
|
+
{ command: variable },
|
|
90
|
+
{ char: '.' },
|
|
91
|
+
],
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
case 'rejected_token':
|
|
95
|
+
return {
|
|
96
|
+
message: `The token in ${variable} was rejected: it has been revoked, has expired, or isn't a valid token.`,
|
|
97
|
+
tryMessage: ['Check the value, or generate a new token', ...admin, { char: '.' }],
|
|
98
|
+
};
|
|
99
|
+
case 'unknown':
|
|
100
|
+
return {
|
|
101
|
+
message: `The custom token provided can't be used for the ${prettyName} API.`,
|
|
102
|
+
tryMessage: 'Ensure the token is correct and not expired.',
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
17
106
|
/**
|
|
18
107
|
* Given an identity token, request an application token.
|
|
19
108
|
* @param identityToken - access token obtained in a previous step
|
|
@@ -66,14 +155,14 @@ async function exchangeAppAutomationTokenForAccessToken(apiName, token, scopes)
|
|
|
66
155
|
const accessToken = newToken[appId].accessToken;
|
|
67
156
|
const userId = nonRandomUUID(token);
|
|
68
157
|
setLastSeenUserIdAfterAuth(userId);
|
|
69
|
-
setLastSeenAuthMethod(
|
|
158
|
+
setLastSeenAuthMethod(automationTokenAuthMethod());
|
|
70
159
|
return { accessToken, userId };
|
|
71
160
|
}
|
|
72
161
|
catch (error) {
|
|
73
162
|
const prettyName = apiName.replace(/-/g, ' ').replace(/\b\w/g, (char) => char.toUpperCase());
|
|
74
163
|
const reason = error instanceof Error ? error.message.slice(0, 200) : 'unknown error';
|
|
75
164
|
outputDebug(`Token exchange for the ${prettyName} API failed: ${reason}`);
|
|
76
|
-
throw new
|
|
165
|
+
throw new AutomationTokenExchangeError(apiName, scopes, error);
|
|
77
166
|
}
|
|
78
167
|
}
|
|
79
168
|
/**
|
|
@@ -96,10 +185,11 @@ export async function exchangeAppAutomationTokenForAppManagementAccessToken(toke
|
|
|
96
185
|
/**
|
|
97
186
|
* Given a custom app automation token passed as ENV variable, request a valid Business Platform API token.
|
|
98
187
|
* @param token - The app automation token passed as ENV variable `SHOPIFY_APP_AUTOMATION_TOKEN`
|
|
188
|
+
* @param scopes - The scopes to request. An empty list makes Identity issue every Business Platform scope the token holds.
|
|
99
189
|
* @returns An instance with the application access tokens.
|
|
100
190
|
*/
|
|
101
|
-
export async function exchangeAppAutomationTokenForBusinessPlatformAccessToken(token) {
|
|
102
|
-
return exchangeAppAutomationTokenForAccessToken('business-platform', token,
|
|
191
|
+
export async function exchangeAppAutomationTokenForBusinessPlatformAccessToken(token, scopes = tokenExchangeScopes('business-platform')) {
|
|
192
|
+
return exchangeAppAutomationTokenForAccessToken('business-platform', token, scopes);
|
|
103
193
|
}
|
|
104
194
|
const identityDeviceErrors = [
|
|
105
195
|
'authorization_pending',
|
|
@@ -151,7 +241,7 @@ export async function requestAppToken(api, token, scopes = [], store) {
|
|
|
151
241
|
const appToken = buildApplicationToken(value);
|
|
152
242
|
return { [identifier]: appToken };
|
|
153
243
|
}
|
|
154
|
-
function tokenRequestErrorHandler({ error, store }) {
|
|
244
|
+
function tokenRequestErrorHandler({ error, description, store }) {
|
|
155
245
|
const invalidTargetErrorMessage = `You are not authorized to use the CLI to develop in the provided store${store ? `: ${store}` : '.'}`;
|
|
156
246
|
if (error === 'invalid_grant') {
|
|
157
247
|
// There's an scenario when Identity returns "invalid_grant" when trying to refresh the token
|
|
@@ -161,7 +251,7 @@ function tokenRequestErrorHandler({ error, store }) {
|
|
|
161
251
|
if (error === 'invalid_request') {
|
|
162
252
|
// There's an scenario when Identity returns "invalid_request" when exchanging an identity token.
|
|
163
253
|
// This means the token is invalid. We clear the session and throw an error to let the caller know.
|
|
164
|
-
return new InvalidRequestError();
|
|
254
|
+
return new InvalidRequestError(description);
|
|
165
255
|
}
|
|
166
256
|
if (error === 'invalid_target') {
|
|
167
257
|
return new InvalidTargetError(invalidTargetErrorMessage, '', [
|
|
@@ -196,7 +286,7 @@ async function tokenRequest(params) {
|
|
|
196
286
|
const rawDescription = typeof payload.error_description === 'string' ? payload.error_description : '';
|
|
197
287
|
const oauthDescription = rawDescription.slice(0, 200);
|
|
198
288
|
outputDebug(`Token request to Identity failed with status ${res.status}: ${oauthError}${oauthDescription === '' ? '' : ` - ${oauthDescription}`}`);
|
|
199
|
-
return err({ error: oauthError, store: params.store });
|
|
289
|
+
return err({ error: oauthError, description: oauthDescription, store: params.store });
|
|
200
290
|
}
|
|
201
291
|
catch (error) {
|
|
202
292
|
if (error instanceof SyntaxError) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exchange.js","sourceRoot":"","sources":["../../../../src/private/node/session/exchange.ts"],"names":[],"mappings":"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","sourcesContent":["import {ApplicationToken, IdentityToken} from './schema.js'\nimport {applicationId, clientId as getIdentityClientId} from './identity.js'\nimport {tokenExchangeScopes} from './scopes.js'\nimport {API} from '../api.js'\nimport {identityFqdn} from '../../../public/node/context/fqdn.js'\nimport {shopifyFetch} from '../../../public/node/http.js'\nimport {err, ok, Result} from '../../../public/node/result.js'\nimport {AbortError, BugError} from '../../../public/node/error.js'\nimport {setLastSeenAuthMethod, setLastSeenUserIdAfterAuth} from '../session.js'\nimport {nonRandomUUID} from '../../../public/node/crypto.js'\nimport {outputDebug} from '../../../public/node/output.js'\n\nimport * as jose from 'jose'\n\nexport class InvalidGrantError extends Error {}\nexport class InvalidRequestError extends Error {}\nclass InvalidTargetError extends AbortError {}\n\nexport interface ExchangeScopes {\n admin: string[]\n partners: string[]\n storefront: string[]\n businessPlatform: string[]\n appManagement: string[]\n}\n\n/**\n * Given an identity token, request an application token.\n * @param identityToken - access token obtained in a previous step\n * @param store - the store to use, only needed for admin API\n * @returns An array with the application access tokens.\n */\nexport async function exchangeAccessForApplicationTokens(\n identityToken: IdentityToken,\n scopes: ExchangeScopes,\n store?: string,\n): Promise<Record<string, ApplicationToken>> {\n const token = identityToken.accessToken\n\n const [partners, storefront, businessPlatform, admin, appManagement] = await Promise.all([\n requestAppToken('partners', token, scopes.partners),\n requestAppToken('storefront-renderer', token, scopes.storefront),\n requestAppToken('business-platform', token, scopes.businessPlatform),\n store ? requestAppToken('admin', token, scopes.admin, store) : {},\n requestAppToken('app-management', token, scopes.appManagement),\n ])\n\n return {\n ...partners,\n ...storefront,\n ...businessPlatform,\n ...admin,\n ...appManagement,\n }\n}\n\n/**\n * Given an expired access token, refresh it to get a new one.\n */\nexport async function refreshAccessToken(currentToken: IdentityToken): Promise<IdentityToken> {\n const clientId = getIdentityClientId()\n const params = {\n grant_type: 'refresh_token',\n access_token: currentToken.accessToken,\n refresh_token: currentToken.refreshToken,\n client_id: clientId,\n }\n const tokenResult = await tokenRequest(params)\n const value = tokenResult.mapError(tokenRequestErrorHandler).valueOrBug()\n return buildIdentityToken(value, currentToken.userId, currentToken.alias)\n}\n\n/**\n * Given a custom app automation token passed as ENV variable, request a valid API access token.\n * @param apiName - The API to exchange for the access token\n * @param token - The app automation token passed as ENV variable `SHOPIFY_APP_AUTOMATION_TOKEN`\n * @param scopes - The scopes to request with the access token\n * @returns An instance with the application access tokens.\n */\nasync function exchangeAppAutomationTokenForAccessToken(\n apiName: API,\n token: string,\n scopes: string[],\n): Promise<{accessToken: string; userId: string}> {\n const appId = applicationId(apiName)\n try {\n const newToken = await requestAppToken(apiName, token, scopes)\n\n const accessToken = newToken[appId]!.accessToken\n const userId = nonRandomUUID(token)\n setLastSeenUserIdAfterAuth(userId)\n setLastSeenAuthMethod('partners_token')\n return {accessToken, userId}\n } catch (error) {\n const prettyName = apiName.replace(/-/g, ' ').replace(/\\b\\w/g, (char) => char.toUpperCase())\n const reason = error instanceof Error ? error.message.slice(0, 200) : 'unknown error'\n outputDebug(`Token exchange for the ${prettyName} API failed: ${reason}`)\n throw new AbortError(\n `The custom token provided can't be used for the ${prettyName} API.`,\n 'Ensure the token is correct and not expired.',\n )\n }\n}\n\n/**\n * Given a custom app automation token passed as ENV variable, request a valid Partners API token.\n * This token does not accept extra scopes, just the cli one.\n * @param token - The app automation token passed as ENV variable `SHOPIFY_APP_AUTOMATION_TOKEN`\n * @returns An instance with the application access tokens.\n */\nexport async function exchangeCustomPartnerToken(token: string): Promise<{accessToken: string; userId: string}> {\n return exchangeAppAutomationTokenForAccessToken('partners', token, tokenExchangeScopes('partners'))\n}\n\n/**\n * Given a custom app automation token passed as ENV variable, request a valid App Management API token.\n * @param token - The app automation token passed as ENV variable `SHOPIFY_APP_AUTOMATION_TOKEN`\n * @returns An instance with the application access tokens.\n */\nexport async function exchangeAppAutomationTokenForAppManagementAccessToken(\n token: string,\n): Promise<{accessToken: string; userId: string}> {\n return exchangeAppAutomationTokenForAccessToken('app-management', token, tokenExchangeScopes('app-management'))\n}\n\n/**\n * Given a custom app automation token passed as ENV variable, request a valid Business Platform API token.\n * @param token - The app automation token passed as ENV variable `SHOPIFY_APP_AUTOMATION_TOKEN`\n * @returns An instance with the application access tokens.\n */\nexport async function exchangeAppAutomationTokenForBusinessPlatformAccessToken(\n token: string,\n): Promise<{accessToken: string; userId: string}> {\n return exchangeAppAutomationTokenForAccessToken('business-platform', token, tokenExchangeScopes('business-platform'))\n}\n\nconst identityDeviceErrors = [\n 'authorization_pending',\n 'access_denied',\n 'expired_token',\n 'slow_down',\n 'unknown_failure',\n] as const\n\ntype IdentityDeviceError = (typeof identityDeviceErrors)[number]\n\n/**\n * Given a deviceCode obtained after starting a device identity flow, request an identity token.\n * @param deviceCode - The device code obtained after starting a device identity flow\n * @param scopes - The scopes to request\n * @returns An instance with the identity access tokens.\n */\nexport async function exchangeDeviceCodeForAccessToken(\n deviceCode: string,\n): Promise<Result<IdentityToken, IdentityDeviceError>> {\n const clientId = await getIdentityClientId()\n\n const params = {\n grant_type: 'urn:ietf:params:oauth:grant-type:device_code',\n device_code: deviceCode,\n client_id: clientId,\n }\n\n const tokenResult = await tokenRequest(params)\n if (tokenResult.isErr()) {\n const deviceError = identityDeviceErrors.find((code) => code === tokenResult.error.error) ?? 'unknown_failure'\n return err(deviceError)\n }\n const identityToken = buildIdentityToken(tokenResult.value)\n return ok(identityToken)\n}\n\nexport async function requestAppToken(\n api: API,\n token: string,\n scopes: string[] = [],\n store?: string,\n): Promise<Record<string, ApplicationToken>> {\n const appId = applicationId(api)\n const clientId = await getIdentityClientId()\n\n const params = {\n grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',\n requested_token_type: 'urn:ietf:params:oauth:token-type:access_token',\n subject_token_type: 'urn:ietf:params:oauth:token-type:access_token',\n client_id: clientId,\n audience: appId,\n scope: scopes.join(' '),\n subject_token: token,\n ...(api === 'admin' && {destination: `https://${store}/admin`, store}),\n }\n\n let identifier = appId\n if (api === 'admin' && store) {\n identifier = `${store}-${appId}`\n }\n const tokenResult = await tokenRequest(params)\n const value = tokenResult.mapError(tokenRequestErrorHandler).valueOrBug()\n const appToken = buildApplicationToken(value)\n return {[identifier]: appToken}\n}\n\ninterface TokenRequestResult {\n access_token: string\n expires_in: number\n refresh_token: string\n scope: string\n id_token?: string\n}\n\nfunction tokenRequestErrorHandler({error, store}: TokenRequestError) {\n const invalidTargetErrorMessage = `You are not authorized to use the CLI to develop in the provided store${\n store ? `: ${store}` : '.'\n }`\n\n if (error === 'invalid_grant') {\n // There's an scenario when Identity returns \"invalid_grant\" when trying to refresh the token\n // using a valid refresh token. When that happens, we take the user through the authentication flow.\n return new InvalidGrantError()\n }\n if (error === 'invalid_request') {\n // There's an scenario when Identity returns \"invalid_request\" when exchanging an identity token.\n // This means the token is invalid. We clear the session and throw an error to let the caller know.\n return new InvalidRequestError()\n }\n if (error === 'invalid_target') {\n return new InvalidTargetError(invalidTargetErrorMessage, '', [\n 'Ensure you have logged in to the store using the Shopify admin at least once.',\n 'Ensure you are the store owner, or have a staff account if you are attempting to log in to a dev store.',\n 'Ensure you are using the permanent store domain, not a vanity domain.',\n ])\n }\n // eslint-disable-next-line @shopify/cli/no-error-factory-functions\n return new AbortError(error)\n}\n\ninterface TokenRequestError {\n error: string\n store?: string\n}\n\nasync function tokenRequest(params: Record<string, string>): Promise<Result<TokenRequestResult, TokenRequestError>> {\n const fqdn = await identityFqdn()\n const url = `https://${fqdn}/oauth/token`\n\n // Send OAuth parameters in the request body (per RFC 6749) rather than the\n // URL query string. This prevents sensitive credentials (subject_token,\n // refresh_token, device_code, client_id, etc.) from being written to\n // verbose CLI debug output or otherwise leaking through URLs.\n const body = new URLSearchParams(Object.entries(params)).toString()\n\n const res = await shopifyFetch(url, {\n method: 'POST',\n headers: {'Content-Type': 'application/x-www-form-urlencoded'},\n body,\n })\n try {\n const responseText = await res.text()\n\n const payload = JSON.parse(responseText)\n if (res.ok) return ok(payload)\n\n // Normalize malformed OAuth error responses before logging.\n const oauthError = typeof payload.error === 'string' && payload.error !== '' ? payload.error : 'unknown_error'\n const rawDescription = typeof payload.error_description === 'string' ? payload.error_description : ''\n const oauthDescription = rawDescription.slice(0, 200)\n\n outputDebug(\n `Token request to Identity failed with status ${res.status}: ${oauthError}${\n oauthDescription === '' ? '' : ` - ${oauthDescription}`\n }`,\n )\n\n return err({error: oauthError, store: params.store})\n } catch (error) {\n if (error instanceof SyntaxError) {\n throw new AbortError(\n `Received invalid response from authentication service (HTTP ${res.status}).`,\n 'The response could not be parsed as JSON. The service may be temporarily unavailable. Please try again.',\n )\n }\n throw error\n }\n}\n\nfunction buildIdentityToken(\n result: TokenRequestResult,\n existingUserId?: string,\n existingAlias?: string,\n): IdentityToken {\n const userId = existingUserId ?? (result.id_token ? jose.decodeJwt(result.id_token).sub! : undefined)\n\n if (!userId) {\n throw new BugError('Error setting userId for session. No id_token or pre-existing user ID provided.')\n }\n\n return {\n accessToken: result.access_token,\n refreshToken: result.refresh_token,\n expiresAt: new Date(Date.now() + result.expires_in * 1000),\n scopes: result.scope.split(' '),\n userId,\n alias: existingAlias,\n }\n}\n\nfunction buildApplicationToken(result: TokenRequestResult): ApplicationToken {\n return {\n accessToken: result.access_token,\n expiresAt: new Date(Date.now() + result.expires_in * 1000),\n scopes: result.scope.split(' '),\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"exchange.js","sourceRoot":"","sources":["../../../../src/private/node/session/exchange.ts"],"names":[],"mappings":"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","sourcesContent":["import {ApplicationToken, IdentityToken} from './schema.js'\nimport {applicationId, clientId as getIdentityClientId} from './identity.js'\nimport {tokenExchangeScopes} from './scopes.js'\nimport {automationTokenAuthMethod, automationTokenVariable} from './automation-token.js'\nimport {environmentVariables} from '../constants.js'\nimport {API} from '../api.js'\nimport {identityFqdn} from '../../../public/node/context/fqdn.js'\nimport {shopifyFetch} from '../../../public/node/http.js'\nimport {err, ok, Result} from '../../../public/node/result.js'\nimport {AbortError, BugError} from '../../../public/node/error.js'\nimport {type TokenItem} from '../../../public/node/ui.js'\nimport {setLastSeenAuthMethod, setLastSeenUserIdAfterAuth} from '../session.js'\nimport {nonRandomUUID} from '../../../public/node/crypto.js'\nimport {outputDebug} from '../../../public/node/output.js'\n\nimport * as jose from 'jose'\n\nexport class InvalidGrantError extends Error {}\nexport class InvalidRequestError extends Error {\n /** Identity's `error_description`, when it sent one. Says what about the request was invalid. */\n readonly description: string\n\n constructor(description = '') {\n super()\n this.description = description\n }\n}\nclass InvalidTargetError extends AbortError {}\n\n/**\n * Why Identity refused to exchange an automation token, read from its `error_description`.\n *\n * - `missing_access`: the token doesn't hold a scope the exchange asked for, so the account lacks that capability.\n * - `rejected_token`: the token itself was refused: revoked, expired, malformed, or not a token Identity knows.\n * - `unknown`: anything else, including a network failure.\n */\ntype AutomationTokenExchangeFailure = 'missing_access' | 'rejected_token' | 'unknown'\n\nfunction automationTokenExchangeFailure(error: unknown): AutomationTokenExchangeFailure {\n if (!(error instanceof InvalidRequestError)) return 'unknown'\n // Identity's two ways of saying the token holds no scope the exchange asked for. Its third 'scope' message,\n // \"specified scopes cannot be granted to this audience\", is about the CLI's own request, so it stays generic.\n if (\n error.description.includes(\"Invalid 'scope' value: new scopes must not be specified\") ||\n error.description.includes(\"Invalid 'scope' value: no useable scope\")\n ) {\n return 'missing_access'\n }\n if (error.description.includes(\"Invalid 'subject_token' value\")) return 'rejected_token'\n return 'unknown'\n}\n\n/**\n * The capability a service account needs for the scopes an exchange asks for, named as the Shopify admin names it.\n */\nfunction capabilityForScopes(scopes: string[]): string | undefined {\n if (scopes.includes('https://api.shopify.com/auth/organization.apps.manage')) return 'App development'\n if (scopes.includes('https://api.shopify.com/auth/organization.store-management')) return 'Dev store management'\n return undefined\n}\n\n/**\n * The error shown when an automation token can't be exchanged for an API's access token.\n *\n * Says what Identity refused, so a token that merely lacks a capability isn't reported as wrong or expired.\n */\nclass AutomationTokenExchangeError extends AbortError {\n constructor(apiName: API, scopes: string[], cause: unknown) {\n const {message, tryMessage} = automationTokenExchangeMessages(apiName, scopes, cause)\n super(message, tryMessage)\n }\n}\n\nfunction automationTokenExchangeMessages(\n apiName: API,\n scopes: string[],\n cause: unknown,\n): {message: string; tryMessage: TokenItem} {\n const prettyName = apiName.replace(/-/g, ' ').replace(/\\b\\w/g, (char) => char.toUpperCase())\n const variable = automationTokenVariable() ?? environmentVariables.serviceAccountToken\n const admin: TokenItem = ['in the Shopify admin under', {subdued: 'Settings > Users > Security'}]\n\n switch (automationTokenExchangeFailure(cause)) {\n case 'missing_access': {\n const capability = capabilityForScopes(scopes)\n const access = capability ? `${capability} access` : `the access the ${prettyName} API needs`\n if (variable === environmentVariables.serviceAccountToken) {\n return {\n message: `The service account behind ${variable} doesn't have ${access}.`,\n tryMessage: [\n 'Give it',\n ...(capability ? [{bold: capability}, 'access'] : ['that access']),\n ...admin,\n {char: ','},\n 'or use a token from a service account that has it.',\n ],\n }\n }\n // Setting the new variable beside the old one is refused as a conflict, so say to unset it too.\n return {\n message: `The token in ${variable} doesn't have ${access}.`,\n tryMessage: [\n 'Use a service account token that has it: create one',\n ...admin,\n {char: ','},\n 'set it in',\n {command: environmentVariables.serviceAccountToken},\n 'and unset',\n {command: variable},\n {char: '.'},\n ],\n }\n }\n case 'rejected_token':\n return {\n message: `The token in ${variable} was rejected: it has been revoked, has expired, or isn't a valid token.`,\n tryMessage: ['Check the value, or generate a new token', ...admin, {char: '.'}],\n }\n case 'unknown':\n return {\n message: `The custom token provided can't be used for the ${prettyName} API.`,\n tryMessage: 'Ensure the token is correct and not expired.',\n }\n }\n}\n\nexport interface ExchangeScopes {\n admin: string[]\n partners: string[]\n storefront: string[]\n businessPlatform: string[]\n appManagement: string[]\n}\n\n/**\n * Given an identity token, request an application token.\n * @param identityToken - access token obtained in a previous step\n * @param store - the store to use, only needed for admin API\n * @returns An array with the application access tokens.\n */\nexport async function exchangeAccessForApplicationTokens(\n identityToken: IdentityToken,\n scopes: ExchangeScopes,\n store?: string,\n): Promise<Record<string, ApplicationToken>> {\n const token = identityToken.accessToken\n\n const [partners, storefront, businessPlatform, admin, appManagement] = await Promise.all([\n requestAppToken('partners', token, scopes.partners),\n requestAppToken('storefront-renderer', token, scopes.storefront),\n requestAppToken('business-platform', token, scopes.businessPlatform),\n store ? requestAppToken('admin', token, scopes.admin, store) : {},\n requestAppToken('app-management', token, scopes.appManagement),\n ])\n\n return {\n ...partners,\n ...storefront,\n ...businessPlatform,\n ...admin,\n ...appManagement,\n }\n}\n\n/**\n * Given an expired access token, refresh it to get a new one.\n */\nexport async function refreshAccessToken(currentToken: IdentityToken): Promise<IdentityToken> {\n const clientId = getIdentityClientId()\n const params = {\n grant_type: 'refresh_token',\n access_token: currentToken.accessToken,\n refresh_token: currentToken.refreshToken,\n client_id: clientId,\n }\n const tokenResult = await tokenRequest(params)\n const value = tokenResult.mapError(tokenRequestErrorHandler).valueOrBug()\n return buildIdentityToken(value, currentToken.userId, currentToken.alias)\n}\n\n/**\n * Given a custom app automation token passed as ENV variable, request a valid API access token.\n * @param apiName - The API to exchange for the access token\n * @param token - The app automation token passed as ENV variable `SHOPIFY_APP_AUTOMATION_TOKEN`\n * @param scopes - The scopes to request with the access token\n * @returns An instance with the application access tokens.\n */\nasync function exchangeAppAutomationTokenForAccessToken(\n apiName: API,\n token: string,\n scopes: string[],\n): Promise<{accessToken: string; userId: string}> {\n const appId = applicationId(apiName)\n try {\n const newToken = await requestAppToken(apiName, token, scopes)\n\n const accessToken = newToken[appId]!.accessToken\n const userId = nonRandomUUID(token)\n setLastSeenUserIdAfterAuth(userId)\n setLastSeenAuthMethod(automationTokenAuthMethod())\n return {accessToken, userId}\n } catch (error) {\n const prettyName = apiName.replace(/-/g, ' ').replace(/\\b\\w/g, (char) => char.toUpperCase())\n const reason = error instanceof Error ? error.message.slice(0, 200) : 'unknown error'\n outputDebug(`Token exchange for the ${prettyName} API failed: ${reason}`)\n throw new AutomationTokenExchangeError(apiName, scopes, error)\n }\n}\n\n/**\n * Given a custom app automation token passed as ENV variable, request a valid Partners API token.\n * This token does not accept extra scopes, just the cli one.\n * @param token - The app automation token passed as ENV variable `SHOPIFY_APP_AUTOMATION_TOKEN`\n * @returns An instance with the application access tokens.\n */\nexport async function exchangeCustomPartnerToken(token: string): Promise<{accessToken: string; userId: string}> {\n return exchangeAppAutomationTokenForAccessToken('partners', token, tokenExchangeScopes('partners'))\n}\n\n/**\n * Given a custom app automation token passed as ENV variable, request a valid App Management API token.\n * @param token - The app automation token passed as ENV variable `SHOPIFY_APP_AUTOMATION_TOKEN`\n * @returns An instance with the application access tokens.\n */\nexport async function exchangeAppAutomationTokenForAppManagementAccessToken(\n token: string,\n): Promise<{accessToken: string; userId: string}> {\n return exchangeAppAutomationTokenForAccessToken('app-management', token, tokenExchangeScopes('app-management'))\n}\n\n/**\n * Given a custom app automation token passed as ENV variable, request a valid Business Platform API token.\n * @param token - The app automation token passed as ENV variable `SHOPIFY_APP_AUTOMATION_TOKEN`\n * @param scopes - The scopes to request. An empty list makes Identity issue every Business Platform scope the token holds.\n * @returns An instance with the application access tokens.\n */\nexport async function exchangeAppAutomationTokenForBusinessPlatformAccessToken(\n token: string,\n scopes: string[] = tokenExchangeScopes('business-platform'),\n): Promise<{accessToken: string; userId: string}> {\n return exchangeAppAutomationTokenForAccessToken('business-platform', token, scopes)\n}\n\nconst identityDeviceErrors = [\n 'authorization_pending',\n 'access_denied',\n 'expired_token',\n 'slow_down',\n 'unknown_failure',\n] as const\n\ntype IdentityDeviceError = (typeof identityDeviceErrors)[number]\n\n/**\n * Given a deviceCode obtained after starting a device identity flow, request an identity token.\n * @param deviceCode - The device code obtained after starting a device identity flow\n * @param scopes - The scopes to request\n * @returns An instance with the identity access tokens.\n */\nexport async function exchangeDeviceCodeForAccessToken(\n deviceCode: string,\n): Promise<Result<IdentityToken, IdentityDeviceError>> {\n const clientId = await getIdentityClientId()\n\n const params = {\n grant_type: 'urn:ietf:params:oauth:grant-type:device_code',\n device_code: deviceCode,\n client_id: clientId,\n }\n\n const tokenResult = await tokenRequest(params)\n if (tokenResult.isErr()) {\n const deviceError = identityDeviceErrors.find((code) => code === tokenResult.error.error) ?? 'unknown_failure'\n return err(deviceError)\n }\n const identityToken = buildIdentityToken(tokenResult.value)\n return ok(identityToken)\n}\n\nexport async function requestAppToken(\n api: API,\n token: string,\n scopes: string[] = [],\n store?: string,\n): Promise<Record<string, ApplicationToken>> {\n const appId = applicationId(api)\n const clientId = await getIdentityClientId()\n\n const params = {\n grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',\n requested_token_type: 'urn:ietf:params:oauth:token-type:access_token',\n subject_token_type: 'urn:ietf:params:oauth:token-type:access_token',\n client_id: clientId,\n audience: appId,\n scope: scopes.join(' '),\n subject_token: token,\n ...(api === 'admin' && {destination: `https://${store}/admin`, store}),\n }\n\n let identifier = appId\n if (api === 'admin' && store) {\n identifier = `${store}-${appId}`\n }\n const tokenResult = await tokenRequest(params)\n const value = tokenResult.mapError(tokenRequestErrorHandler).valueOrBug()\n const appToken = buildApplicationToken(value)\n return {[identifier]: appToken}\n}\n\ninterface TokenRequestResult {\n access_token: string\n expires_in: number\n refresh_token: string\n scope: string\n id_token?: string\n}\n\nfunction tokenRequestErrorHandler({error, description, store}: TokenRequestError) {\n const invalidTargetErrorMessage = `You are not authorized to use the CLI to develop in the provided store${\n store ? `: ${store}` : '.'\n }`\n\n if (error === 'invalid_grant') {\n // There's an scenario when Identity returns \"invalid_grant\" when trying to refresh the token\n // using a valid refresh token. When that happens, we take the user through the authentication flow.\n return new InvalidGrantError()\n }\n if (error === 'invalid_request') {\n // There's an scenario when Identity returns \"invalid_request\" when exchanging an identity token.\n // This means the token is invalid. We clear the session and throw an error to let the caller know.\n return new InvalidRequestError(description)\n }\n if (error === 'invalid_target') {\n return new InvalidTargetError(invalidTargetErrorMessage, '', [\n 'Ensure you have logged in to the store using the Shopify admin at least once.',\n 'Ensure you are the store owner, or have a staff account if you are attempting to log in to a dev store.',\n 'Ensure you are using the permanent store domain, not a vanity domain.',\n ])\n }\n // eslint-disable-next-line @shopify/cli/no-error-factory-functions\n return new AbortError(error)\n}\n\ninterface TokenRequestError {\n error: string\n /** Identity's `error_description`, truncated. Empty when it sent none. */\n description: string\n store?: string\n}\n\nasync function tokenRequest(params: Record<string, string>): Promise<Result<TokenRequestResult, TokenRequestError>> {\n const fqdn = await identityFqdn()\n const url = `https://${fqdn}/oauth/token`\n\n // Send OAuth parameters in the request body (per RFC 6749) rather than the\n // URL query string. This prevents sensitive credentials (subject_token,\n // refresh_token, device_code, client_id, etc.) from being written to\n // verbose CLI debug output or otherwise leaking through URLs.\n const body = new URLSearchParams(Object.entries(params)).toString()\n\n const res = await shopifyFetch(url, {\n method: 'POST',\n headers: {'Content-Type': 'application/x-www-form-urlencoded'},\n body,\n })\n try {\n const responseText = await res.text()\n\n const payload = JSON.parse(responseText)\n if (res.ok) return ok(payload)\n\n // Normalize malformed OAuth error responses before logging.\n const oauthError = typeof payload.error === 'string' && payload.error !== '' ? payload.error : 'unknown_error'\n const rawDescription = typeof payload.error_description === 'string' ? payload.error_description : ''\n const oauthDescription = rawDescription.slice(0, 200)\n\n outputDebug(\n `Token request to Identity failed with status ${res.status}: ${oauthError}${\n oauthDescription === '' ? '' : ` - ${oauthDescription}`\n }`,\n )\n\n return err({error: oauthError, description: oauthDescription, store: params.store})\n } catch (error) {\n if (error instanceof SyntaxError) {\n throw new AbortError(\n `Received invalid response from authentication service (HTTP ${res.status}).`,\n 'The response could not be parsed as JSON. The service may be temporarily unavailable. Please try again.',\n )\n }\n throw error\n }\n}\n\nfunction buildIdentityToken(\n result: TokenRequestResult,\n existingUserId?: string,\n existingAlias?: string,\n): IdentityToken {\n const userId = existingUserId ?? (result.id_token ? jose.decodeJwt(result.id_token).sub! : undefined)\n\n if (!userId) {\n throw new BugError('Error setting userId for session. No id_token or pre-existing user ID provided.')\n }\n\n return {\n accessToken: result.access_token,\n refreshToken: result.refresh_token,\n expiresAt: new Date(Date.now() + result.expires_in * 1000),\n scopes: result.scope.split(' '),\n userId,\n alias: existingAlias,\n }\n}\n\nfunction buildApplicationToken(result: TokenRequestResult): ApplicationToken {\n return {\n accessToken: result.access_token,\n expiresAt: new Date(Date.now() + result.expires_in * 1000),\n scopes: result.scope.split(' '),\n }\n}\n"]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves the permanent `.myshopify.com` domain of a store.
|
|
3
|
+
*
|
|
4
|
+
* A store can be reached through several domains (a renamed `.myshopify.com` domain, a custom domain), but some
|
|
5
|
+
* Shopify services only recognise the store's permanent domain. The Theme Access app is one of them: its passwords
|
|
6
|
+
* are tied to the permanent domain, so a request made with any other domain fails with a 401.
|
|
7
|
+
*
|
|
8
|
+
* The permanent domain is read from the store's public `/meta.json` endpoint, which needs no authentication and is
|
|
9
|
+
* served even when the storefront is password protected. When the lookup fails for any reason, the domain is
|
|
10
|
+
* returned unchanged, so callers behave exactly as they would without this lookup.
|
|
11
|
+
*
|
|
12
|
+
* Results are cached for the lifetime of the process.
|
|
13
|
+
*
|
|
14
|
+
* @param storeFqdn - The store domain the user provided, for example `my-store.myshopify.com`.
|
|
15
|
+
* @returns The store's permanent domain, or `storeFqdn` when it cannot be determined.
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolvePermanentStoreFqdn(storeFqdn: string): Promise<string>;
|
|
18
|
+
/**
|
|
19
|
+
* Clears the in-process cache used by {@link resolvePermanentStoreFqdn}. Intended for tests.
|
|
20
|
+
*/
|
|
21
|
+
export declare function clearPermanentStoreFqdnCache(): void;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { serviceEnvironment } from '../context/service.js';
|
|
2
|
+
import { extractMyshopifyHandle } from '../../../public/common/url.js';
|
|
3
|
+
import { fetch } from '../../../public/node/http.js';
|
|
4
|
+
import { outputContent, outputDebug, outputInfo, outputToken } from '../../../public/node/output.js';
|
|
5
|
+
/**
|
|
6
|
+
* How long to wait for a store's public `/meta.json` before giving up and using the domain as given.
|
|
7
|
+
*/
|
|
8
|
+
const PERMANENT_DOMAIN_LOOKUP_TIMEOUT_MS = 5000;
|
|
9
|
+
const resolvedStoreFqdns = new Map();
|
|
10
|
+
/**
|
|
11
|
+
* Resolves the permanent `.myshopify.com` domain of a store.
|
|
12
|
+
*
|
|
13
|
+
* A store can be reached through several domains (a renamed `.myshopify.com` domain, a custom domain), but some
|
|
14
|
+
* Shopify services only recognise the store's permanent domain. The Theme Access app is one of them: its passwords
|
|
15
|
+
* are tied to the permanent domain, so a request made with any other domain fails with a 401.
|
|
16
|
+
*
|
|
17
|
+
* The permanent domain is read from the store's public `/meta.json` endpoint, which needs no authentication and is
|
|
18
|
+
* served even when the storefront is password protected. When the lookup fails for any reason, the domain is
|
|
19
|
+
* returned unchanged, so callers behave exactly as they would without this lookup.
|
|
20
|
+
*
|
|
21
|
+
* Results are cached for the lifetime of the process.
|
|
22
|
+
*
|
|
23
|
+
* @param storeFqdn - The store domain the user provided, for example `my-store.myshopify.com`.
|
|
24
|
+
* @returns The store's permanent domain, or `storeFqdn` when it cannot be determined.
|
|
25
|
+
*/
|
|
26
|
+
export async function resolvePermanentStoreFqdn(storeFqdn) {
|
|
27
|
+
if (serviceEnvironment() === 'local')
|
|
28
|
+
return storeFqdn;
|
|
29
|
+
const cached = resolvedStoreFqdns.get(storeFqdn);
|
|
30
|
+
if (cached)
|
|
31
|
+
return cached;
|
|
32
|
+
const permanentStoreFqdn = await fetchPermanentStoreFqdn(storeFqdn);
|
|
33
|
+
resolvedStoreFqdns.set(storeFqdn, permanentStoreFqdn);
|
|
34
|
+
if (permanentStoreFqdn !== storeFqdn) {
|
|
35
|
+
resolvedStoreFqdns.set(permanentStoreFqdn, permanentStoreFqdn);
|
|
36
|
+
outputInfo(outputContent `Using the permanent domain ${outputToken.raw(permanentStoreFqdn)} for ${outputToken.raw(storeFqdn)}.`);
|
|
37
|
+
}
|
|
38
|
+
return permanentStoreFqdn;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Clears the in-process cache used by {@link resolvePermanentStoreFqdn}. Intended for tests.
|
|
42
|
+
*/
|
|
43
|
+
export function clearPermanentStoreFqdnCache() {
|
|
44
|
+
resolvedStoreFqdns.clear();
|
|
45
|
+
}
|
|
46
|
+
async function fetchPermanentStoreFqdn(storeFqdn) {
|
|
47
|
+
const url = `https://${storeFqdn}/meta.json`;
|
|
48
|
+
try {
|
|
49
|
+
const response = await fetch(url, { headers: { Accept: 'application/json' } }, { useNetworkLevelRetry: false, useAbortSignal: true, timeoutMs: PERMANENT_DOMAIN_LOOKUP_TIMEOUT_MS });
|
|
50
|
+
if (!response.ok) {
|
|
51
|
+
outputDebug(`Could not look up the permanent domain of ${storeFqdn}: ${url} returned HTTP ${response.status}`);
|
|
52
|
+
return storeFqdn;
|
|
53
|
+
}
|
|
54
|
+
const body = (await response.json());
|
|
55
|
+
const permanentStoreFqdn = typeof body?.myshopify_domain === 'string' ? body.myshopify_domain.toLowerCase() : '';
|
|
56
|
+
// Accept only a bare `<handle>.myshopify.com`: `extractMyshopifyHandle` also accepts a URL with a path, so
|
|
57
|
+
// compare the round trip to reject anything but the domain itself.
|
|
58
|
+
const handle = extractMyshopifyHandle(permanentStoreFqdn);
|
|
59
|
+
if (!handle || `${handle}.myshopify.com` !== permanentStoreFqdn) {
|
|
60
|
+
outputDebug(`Could not look up the permanent domain of ${storeFqdn}: ${url} has no valid myshopify_domain`);
|
|
61
|
+
return storeFqdn;
|
|
62
|
+
}
|
|
63
|
+
return permanentStoreFqdn;
|
|
64
|
+
// eslint-disable-next-line no-catch-all/no-catch-all
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
68
|
+
outputDebug(`Could not look up the permanent domain of ${storeFqdn}: ${message}`);
|
|
69
|
+
return storeFqdn;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=permanent-store-domain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permanent-store-domain.js","sourceRoot":"","sources":["../../../../src/private/node/session/permanent-store-domain.ts"],"names":[],"mappings":"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","sourcesContent":["import {serviceEnvironment} from '../context/service.js'\nimport {extractMyshopifyHandle} from '../../../public/common/url.js'\nimport {fetch} from '../../../public/node/http.js'\nimport {outputContent, outputDebug, outputInfo, outputToken} from '../../../public/node/output.js'\n\n/**\n * How long to wait for a store's public `/meta.json` before giving up and using the domain as given.\n */\nconst PERMANENT_DOMAIN_LOOKUP_TIMEOUT_MS = 5000\n\nconst resolvedStoreFqdns = new Map<string, string>()\n\n/**\n * Resolves the permanent `.myshopify.com` domain of a store.\n *\n * A store can be reached through several domains (a renamed `.myshopify.com` domain, a custom domain), but some\n * Shopify services only recognise the store's permanent domain. The Theme Access app is one of them: its passwords\n * are tied to the permanent domain, so a request made with any other domain fails with a 401.\n *\n * The permanent domain is read from the store's public `/meta.json` endpoint, which needs no authentication and is\n * served even when the storefront is password protected. When the lookup fails for any reason, the domain is\n * returned unchanged, so callers behave exactly as they would without this lookup.\n *\n * Results are cached for the lifetime of the process.\n *\n * @param storeFqdn - The store domain the user provided, for example `my-store.myshopify.com`.\n * @returns The store's permanent domain, or `storeFqdn` when it cannot be determined.\n */\nexport async function resolvePermanentStoreFqdn(storeFqdn: string): Promise<string> {\n if (serviceEnvironment() === 'local') return storeFqdn\n\n const cached = resolvedStoreFqdns.get(storeFqdn)\n if (cached) return cached\n\n const permanentStoreFqdn = await fetchPermanentStoreFqdn(storeFqdn)\n resolvedStoreFqdns.set(storeFqdn, permanentStoreFqdn)\n\n if (permanentStoreFqdn !== storeFqdn) {\n resolvedStoreFqdns.set(permanentStoreFqdn, permanentStoreFqdn)\n outputInfo(\n outputContent`Using the permanent domain ${outputToken.raw(permanentStoreFqdn)} for ${outputToken.raw(\n storeFqdn,\n )}.`,\n )\n }\n return permanentStoreFqdn\n}\n\n/**\n * Clears the in-process cache used by {@link resolvePermanentStoreFqdn}. Intended for tests.\n */\nexport function clearPermanentStoreFqdnCache(): void {\n resolvedStoreFqdns.clear()\n}\n\nasync function fetchPermanentStoreFqdn(storeFqdn: string): Promise<string> {\n const url = `https://${storeFqdn}/meta.json`\n try {\n const response = await fetch(\n url,\n {headers: {Accept: 'application/json'}},\n {useNetworkLevelRetry: false, useAbortSignal: true, timeoutMs: PERMANENT_DOMAIN_LOOKUP_TIMEOUT_MS},\n )\n if (!response.ok) {\n outputDebug(`Could not look up the permanent domain of ${storeFqdn}: ${url} returned HTTP ${response.status}`)\n return storeFqdn\n }\n\n const body = (await response.json()) as {myshopify_domain?: unknown} | null\n const permanentStoreFqdn = typeof body?.myshopify_domain === 'string' ? body.myshopify_domain.toLowerCase() : ''\n // Accept only a bare `<handle>.myshopify.com`: `extractMyshopifyHandle` also accepts a URL with a path, so\n // compare the round trip to reject anything but the domain itself.\n const handle = extractMyshopifyHandle(permanentStoreFqdn)\n if (!handle || `${handle}.myshopify.com` !== permanentStoreFqdn) {\n outputDebug(`Could not look up the permanent domain of ${storeFqdn}: ${url} has no valid myshopify_domain`)\n return storeFqdn\n }\n\n return permanentStoreFqdn\n // eslint-disable-next-line no-catch-all/no-catch-all\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n outputDebug(`Could not look up the permanent domain of ${storeFqdn}: ${message}`)\n return storeFqdn\n }\n}\n"]}
|
|
@@ -61,7 +61,7 @@ export interface OAuthSession {
|
|
|
61
61
|
appManagement?: string;
|
|
62
62
|
userId: string;
|
|
63
63
|
}
|
|
64
|
-
type AuthMethod = 'partners_token' | 'device_auth' | 'theme_access_token' | 'custom_app_token' | 'none';
|
|
64
|
+
type AuthMethod = 'partners_token' | 'service_account_token' | 'device_auth' | 'theme_access_token' | 'custom_app_token' | 'none';
|
|
65
65
|
/**
|
|
66
66
|
* Retrieves a stable user identifier for analytics, or `'unknown'` if none applies.
|
|
67
67
|
*
|
|
@@ -82,7 +82,8 @@ export declare function setLastSeenUserIdAfterAuth(id: string): void;
|
|
|
82
82
|
* This function checks for the authentication method in the following order:
|
|
83
83
|
* 1. Returns the cached auth method if it's not 'none'.
|
|
84
84
|
* 2. Checks for a cached session, which implies 'device_auth' was used.
|
|
85
|
-
* 3. Checks for
|
|
85
|
+
* 3. Checks for an automation token in the environment: `service_account_token` for SHOPIFY_SERVICE_ACCOUNT_TOKEN,
|
|
86
|
+
* `partners_token` for the app and Partner variables.
|
|
86
87
|
* 4. Checks for a theme password in the environment.
|
|
87
88
|
* 5. If none of the above are true, returns 'none'.
|
|
88
89
|
*
|
|
@@ -4,6 +4,7 @@ import { allDefaultScopes, apiScopes } from './session/scopes.js';
|
|
|
4
4
|
import { exchangeAccessForApplicationTokens, exchangeCustomPartnerToken, refreshAccessToken, InvalidGrantError, InvalidRequestError, } from './session/exchange.js';
|
|
5
5
|
import * as sessionStore from './session/store.js';
|
|
6
6
|
import { pollForDeviceAuthorization, requestDeviceAuthorization } from './session/device-authorization.js';
|
|
7
|
+
import { automationTokenAuthMethod, automationTokenVariablesProblem } from './session/automation-token.js';
|
|
7
8
|
import { isThemeAccessSession } from './api/rest.js';
|
|
8
9
|
import { getCurrentSessionId, setCurrentSessionId } from './conf-store.js';
|
|
9
10
|
import { UserEmailQueryString } from './api/graphql/business-platform-destinations/user-email.js';
|
|
@@ -12,7 +13,7 @@ import { themeToken } from '../../public/node/context/local.js';
|
|
|
12
13
|
import { AbortError } from '../../public/node/error.js';
|
|
13
14
|
import { normalizeStoreFqdn, identityFqdn } from '../../public/node/context/fqdn.js';
|
|
14
15
|
import { getIdentityTokenInformation, getAppAutomationToken } from '../../public/node/environment.js';
|
|
15
|
-
import { logout } from '../../public/node/session.js';
|
|
16
|
+
import { ensureNoServiceAccountToken, logout } from '../../public/node/session.js';
|
|
16
17
|
import { nonRandomUUID } from '../../public/node/crypto.js';
|
|
17
18
|
import { isEmpty } from '../../public/common/object.js';
|
|
18
19
|
import { businessPlatformRequest } from '../../public/node/api/business-platform.js';
|
|
@@ -69,7 +70,8 @@ export function setLastSeenUserIdAfterAuth(id) {
|
|
|
69
70
|
* This function checks for the authentication method in the following order:
|
|
70
71
|
* 1. Returns the cached auth method if it's not 'none'.
|
|
71
72
|
* 2. Checks for a cached session, which implies 'device_auth' was used.
|
|
72
|
-
* 3. Checks for
|
|
73
|
+
* 3. Checks for an automation token in the environment: `service_account_token` for SHOPIFY_SERVICE_ACCOUNT_TOKEN,
|
|
74
|
+
* `partners_token` for the app and Partner variables.
|
|
73
75
|
* 4. Checks for a theme password in the environment.
|
|
74
76
|
* 5. If none of the above are true, returns 'none'.
|
|
75
77
|
*
|
|
@@ -82,7 +84,7 @@ export async function getLastSeenAuthMethod() {
|
|
|
82
84
|
return 'device_auth';
|
|
83
85
|
const appAutomationToken = getAppAutomationToken();
|
|
84
86
|
if (appAutomationToken)
|
|
85
|
-
return
|
|
87
|
+
return automationTokenAuthMethod();
|
|
86
88
|
const themePassword = themeToken();
|
|
87
89
|
if (themePassword) {
|
|
88
90
|
return isThemeAccessSession({ token: themePassword, storeFqdn: '' }) ? 'theme_access_token' : 'custom_app_token';
|
|
@@ -104,6 +106,13 @@ export function setCommandSessionId(sessionId) {
|
|
|
104
106
|
* @returns An instance with the access tokens organized by application.
|
|
105
107
|
*/
|
|
106
108
|
export async function ensureAuthenticated(applications, _env, { forceRefresh = false, noPrompt = false, forceNewSession = false } = {}) {
|
|
109
|
+
// Commands that support automation tokens exchange them before calling this function, and getAppAutomationToken
|
|
110
|
+
// returns no token when the variables are invalid, so an invalid environment always reaches this check before any
|
|
111
|
+
// login starts.
|
|
112
|
+
const variablesProblem = automationTokenVariablesProblem();
|
|
113
|
+
if (variablesProblem)
|
|
114
|
+
throw new AbortError(variablesProblem.message, variablesProblem.tryMessage);
|
|
115
|
+
ensureNoServiceAccountToken();
|
|
107
116
|
const fqdn = await identityFqdn();
|
|
108
117
|
const previousStoreFqdn = applications.adminApi?.storeFqdn;
|
|
109
118
|
if (previousStoreFqdn) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../../src/private/node/session.ts"],"names":[],"mappings":"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","sourcesContent":["import {applicationId} from './session/identity.js'\nimport {validateSession} from './session/validate.js'\nimport {allDefaultScopes, apiScopes} from './session/scopes.js'\nimport {\n exchangeAccessForApplicationTokens,\n exchangeCustomPartnerToken,\n ExchangeScopes,\n refreshAccessToken,\n InvalidGrantError,\n InvalidRequestError,\n} from './session/exchange.js'\nimport {IdentityToken, Session, Sessions} from './session/schema.js'\nimport * as sessionStore from './session/store.js'\nimport {pollForDeviceAuthorization, requestDeviceAuthorization} from './session/device-authorization.js'\nimport {isThemeAccessSession} from './api/rest.js'\nimport {getCurrentSessionId, setCurrentSessionId} from './conf-store.js'\nimport {UserEmailQueryString, UserEmailQuery} from './api/graphql/business-platform-destinations/user-email.js'\nimport {outputContent, outputToken, outputDebug, outputCompleted} from '../../public/node/output.js'\nimport {themeToken} from '../../public/node/context/local.js'\nimport {AbortError} from '../../public/node/error.js'\nimport {normalizeStoreFqdn, identityFqdn} from '../../public/node/context/fqdn.js'\nimport {getIdentityTokenInformation, getAppAutomationToken} from '../../public/node/environment.js'\nimport {AdminSession, logout} from '../../public/node/session.js'\nimport {nonRandomUUID} from '../../public/node/crypto.js'\nimport {isEmpty} from '../../public/common/object.js'\nimport {businessPlatformRequest} from '../../public/node/api/business-platform.js'\n\n/**\n * Fetches the user's email from the Business Platform API\n * @param businessPlatformToken - The business platform token\n * @returns The user's email address or undefined if not found\n */\nasync function fetchEmail(businessPlatformToken: string | undefined): Promise<string | undefined> {\n if (!businessPlatformToken) return undefined\n\n try {\n const userEmailResult = await businessPlatformRequest<UserEmailQuery>(UserEmailQueryString, businessPlatformToken)\n return userEmailResult.currentUserAccount?.email ?? undefined\n // eslint-disable-next-line no-catch-all/no-catch-all\n } catch (error) {\n outputDebug(outputContent`Failed to fetch user email: ${(error as Error).message ?? String(error)}`)\n return undefined\n }\n}\n\n/**\n * A scope supported by the Shopify Admin API.\n */\nexport type AdminAPIScope = 'graphql' | 'themes' | 'collaborator'\n\n/**\n * It represents the options to authenticate against the Shopify Admin API.\n */\n\ninterface AdminAPIOAuthOptions {\n /** Store to request permissions for. */\n storeFqdn: string\n /** List of scopes to request permissions for. */\n scopes: AdminAPIScope[]\n}\n\n/**\n * A scope supported by the Partners API.\n */\nexport type PartnersAPIScope = 'cli'\ninterface PartnersAPIOAuthOptions {\n /** List of scopes to request permissions for. */\n scopes: PartnersAPIScope[]\n}\n\n/**\n * A scope supported by the Developer Platform API.\n */\nexport type AppManagementAPIScope = 'https://api.shopify.com/auth/organization.apps.manage'\ninterface AppManagementAPIOauthOptions {\n /** List of scopes to request permissions for. */\n scopes: AppManagementAPIScope[]\n}\n\n/**\n * A scope supported by the Storefront Renderer API.\n */\nexport type StorefrontRendererScope = 'devtools' | 'graphql'\ninterface StorefrontRendererAPIOAuthOptions {\n /** List of scopes to request permissions for. */\n scopes: StorefrontRendererScope[]\n}\n\nexport type BusinessPlatformScope = 'destinations'\ninterface BusinessPlatformAPIOAuthOptions {\n /** List of scopes to request permissions for. */\n scopes: BusinessPlatformScope[]\n}\n\n/**\n * It represents the authentication requirements and\n * is the input necessary to trigger the authentication\n * flow.\n */\nexport interface OAuthApplications {\n adminApi?: AdminAPIOAuthOptions\n storefrontRendererApi?: StorefrontRendererAPIOAuthOptions\n partnersApi?: PartnersAPIOAuthOptions\n businessPlatformApi?: BusinessPlatformAPIOAuthOptions\n appManagementApi?: AppManagementAPIOauthOptions\n}\n\nexport interface OAuthSession {\n admin?: AdminSession\n partners?: string\n storefront?: string\n businessPlatform?: string\n appManagement?: string\n userId: string\n}\n\ntype AuthMethod = 'partners_token' | 'device_auth' | 'theme_access_token' | 'custom_app_token' | 'none'\n\nlet userId: undefined | string\nlet authMethod: AuthMethod = 'none'\nlet commandSessionId: string | undefined\n\n/**\n * Retrieves a stable user identifier for analytics, or `'unknown'` if none applies.\n *\n * Evaluation order:\n * 1. If an app automation token or theme token is used, returns a deterministic UUID\n * derived from that secret.\n * 2. Otherwise, if `setLastSeenUserIdAfterAuth` was called (e.g. after OAuth), returns that value.\n * 3. Otherwise, if a persisted CLI session id is available, returns it.\n * 4. Otherwise returns `'unknown'`.\n *\n * @returns A Promise that resolves to the user ID as a string.\n */\nexport async function getLastSeenUserIdAfterAuth(): Promise<string> {\n const customToken = getAppAutomationToken() ?? themeToken()\n if (customToken) return nonRandomUUID(customToken)\n\n if (userId) return userId\n\n const currentSessionId = getCurrentSessionId()\n if (currentSessionId) return currentSessionId\n\n return 'unknown'\n}\n\nexport function setLastSeenUserIdAfterAuth(id: string) {\n userId = id\n}\n\n/**\n * Retrieves the last seen authentication method used in the current session.\n *\n * This function checks for the authentication method in the following order:\n * 1. Returns the cached auth method if it's not 'none'.\n * 2. Checks for a cached session, which implies 'device_auth' was used.\n * 3. Checks for a partners token in the environment.\n * 4. Checks for a theme password in the environment.\n * 5. If none of the above are true, returns 'none'.\n *\n * @returns A Promise that resolves to the last seen authentication method as an AuthMethod type.\n */\nexport async function getLastSeenAuthMethod(): Promise<AuthMethod> {\n if (authMethod !== 'none') return authMethod\n\n if (getCurrentSessionId()) return 'device_auth'\n\n const appAutomationToken = getAppAutomationToken()\n if (appAutomationToken) return 'partners_token'\n\n const themePassword = themeToken()\n if (themePassword) {\n return isThemeAccessSession({token: themePassword, storeFqdn: ''}) ? 'theme_access_token' : 'custom_app_token'\n }\n\n return 'none'\n}\n\nexport function setLastSeenAuthMethod(method: AuthMethod) {\n authMethod = method\n}\n\nexport function setCommandSessionId(sessionId: string | undefined) {\n commandSessionId = sessionId\n}\n\nexport interface EnsureAuthenticatedAdditionalOptions {\n noPrompt?: boolean\n forceRefresh?: boolean\n forceNewSession?: boolean\n}\n\n/**\n * This method ensures that we have a valid session to authenticate against the given applications using the provided scopes.\n *\n * @param applications - An object containing the applications we need to be authenticated with.\n * @param _env - Optional environment variables to use.\n * @param options - Optional extra options to use.\n * @returns An instance with the access tokens organized by application.\n */\nexport async function ensureAuthenticated(\n applications: OAuthApplications,\n _env?: NodeJS.ProcessEnv,\n {forceRefresh = false, noPrompt = false, forceNewSession = false}: EnsureAuthenticatedAdditionalOptions = {},\n): Promise<OAuthSession> {\n const fqdn = await identityFqdn()\n\n const previousStoreFqdn = applications.adminApi?.storeFqdn\n if (previousStoreFqdn) {\n const normalizedStoreName = normalizeStoreFqdn(previousStoreFqdn)\n if (previousStoreFqdn === applications.adminApi?.storeFqdn) {\n applications.adminApi.storeFqdn = normalizedStoreName\n }\n }\n\n const sessions = (await sessionStore.fetch()) ?? {}\n\n let currentSessionId = forceNewSession ? undefined : (commandSessionId ?? getCurrentSessionId())\n if (!currentSessionId && !commandSessionId) {\n const userIds = Object.keys(sessions[fqdn] ?? {})\n if (userIds.length > 0) currentSessionId = userIds[0]\n }\n const currentSession: Session | undefined =\n currentSessionId && !forceNewSession ? sessions[fqdn]?.[currentSessionId] : undefined\n const scopes = getFlattenScopes(applications)\n\n outputDebug(outputContent`Validating existing session against the scopes:\n${outputToken.json(scopes)}\nFor applications:\n${outputToken.json(applications)}\n`)\n\n const validationResult = await validateSession(scopes, applications, currentSession)\n\n let newSession = {}\n\n if (validationResult === 'needs_full_auth') {\n await throwOnNoPrompt(noPrompt)\n outputDebug(outputContent`Initiating the full authentication flow...`)\n newSession = await executeCompleteFlow(applications, currentSession?.identity.alias)\n } else if (validationResult === 'needs_refresh' || forceRefresh) {\n outputDebug(outputContent`The current session is valid but needs refresh. Refreshing...`)\n try {\n newSession = await refreshTokens(currentSession!, applications)\n } catch (error) {\n if (error instanceof InvalidGrantError) {\n await throwOnNoPrompt(noPrompt)\n newSession = await executeCompleteFlow(applications, currentSession?.identity.alias)\n } else if (error instanceof InvalidRequestError) {\n await sessionStore.remove()\n throw new AbortError('\\nError validating auth session', \"We've cleared the current session, please try again\")\n } else {\n throw error\n }\n }\n }\n\n const completeSession = {...currentSession, ...newSession} as Session\n const newSessionId = completeSession.identity.userId\n const updatedSessions: Sessions = {\n ...sessions,\n [fqdn]: {...sessions[fqdn], [newSessionId]: completeSession},\n }\n\n // Save the new session info if it has changed\n if (!isEmpty(newSession)) {\n await sessionStore.store(updatedSessions)\n if (!commandSessionId) setCurrentSessionId(newSessionId)\n }\n\n const tokens = await tokensFor(applications, completeSession)\n\n const envToken = getAppAutomationToken()\n if (envToken && applications.partnersApi) {\n tokens.partners = (await exchangeCustomPartnerToken(envToken)).accessToken\n }\n\n setLastSeenAuthMethod(envToken ? 'partners_token' : 'device_auth')\n setLastSeenUserIdAfterAuth(tokens.userId)\n return tokens\n}\n\nasync function throwOnNoPrompt(noPrompt: boolean) {\n if (!noPrompt) return\n await logout()\n throw new AbortError(\n `The currently available CLI credentials are invalid.\n\nThe CLI is currently unable to prompt for reauthentication.`,\n 'Restart the CLI process you were running. If in an interactive terminal, you will be prompted to reauthenticate. If in a non-interactive terminal, ensure the correct credentials are available in the program environment.',\n )\n}\n\n/**\n * Execute the full authentication flow.\n *\n * @param applications - An object containing the applications we need to be authenticated with.\n * @param existingAlias - Optional alias from a previous session to preserve if the email fetch fails.\n */\nasync function executeCompleteFlow(applications: OAuthApplications, existingAlias?: string): Promise<Session> {\n const scopes = getFlattenScopes(applications)\n const exchangeScopes = getExchangeScopes(applications)\n const store = applications.adminApi?.storeFqdn\n\n let identityToken: IdentityToken\n const identityTokenInformation = getIdentityTokenInformation()\n if (identityTokenInformation) {\n identityToken = buildIdentityTokenFromEnv(scopes, identityTokenInformation)\n } else {\n // Request a device code to authorize without a browser redirect.\n outputDebug(outputContent`Requesting device authorization code...`)\n const deviceAuth = await requestDeviceAuthorization(scopes)\n\n // Poll for the identity token\n outputDebug(outputContent`Starting polling for the identity token...`)\n identityToken = await pollForDeviceAuthorization(deviceAuth.deviceCode, deviceAuth.interval)\n }\n\n // Exchange identity token for application tokens\n outputDebug(outputContent`CLI token received. Exchanging it for application tokens...`)\n const result = await exchangeAccessForApplicationTokens(identityToken, exchangeScopes, store)\n\n // Preserve existing alias if available, otherwise try fetching email\n const businessPlatformToken = result[applicationId('business-platform')]?.accessToken\n const alias = existingAlias ?? (await fetchEmail(businessPlatformToken)) ?? identityToken.userId\n\n const session: Session = {\n identity: {\n ...identityToken,\n alias,\n },\n applications: result,\n }\n\n outputCompleted(`Logged in.`)\n\n return session\n}\n\n/**\n * Refresh the tokens for a given session.\n *\n * @param session - The session to refresh.\n */\nasync function refreshTokens(session: Session, applications: OAuthApplications): Promise<Session> {\n // Refresh Identity Token\n const identityToken = await refreshAccessToken(session.identity)\n // Exchange new identity token for application tokens\n const exchangeScopes = getExchangeScopes(applications)\n const applicationTokens = await exchangeAccessForApplicationTokens(\n identityToken,\n exchangeScopes,\n applications.adminApi?.storeFqdn,\n )\n\n return {\n identity: {...identityToken, alias: session.identity.alias},\n applications: applicationTokens,\n }\n}\n\n/**\n * Get the application tokens for a given session.\n *\n * @param applications - An object containing the applications we need the tokens for.\n * @param session - The current session.\n * @param fqdn - The identity FQDN.\n */\nasync function tokensFor(applications: OAuthApplications, session: Session): Promise<OAuthSession> {\n const tokens: OAuthSession = {\n userId: session.identity.userId,\n }\n\n if (applications.adminApi) {\n const appId = applicationId('admin')\n const realAppId = `${applications.adminApi.storeFqdn}-${appId}`\n const token = session.applications[realAppId]?.accessToken\n if (token) {\n tokens.admin = {token, storeFqdn: applications.adminApi.storeFqdn}\n }\n }\n\n if (applications.partnersApi) {\n const appId = applicationId('partners')\n tokens.partners = session.applications[appId]?.accessToken\n }\n\n if (applications.storefrontRendererApi) {\n const appId = applicationId('storefront-renderer')\n tokens.storefront = session.applications[appId]?.accessToken\n }\n\n if (applications.businessPlatformApi) {\n const appId = applicationId('business-platform')\n tokens.businessPlatform = session.applications[appId]?.accessToken\n }\n\n if (applications.appManagementApi) {\n const appId = applicationId('app-management')\n tokens.appManagement = session.applications[appId]?.accessToken\n }\n\n return tokens\n}\n\n// Scope Helpers\n/**\n * Get a flattened array of scopes for the given applications.\n *\n * @param apps - An object containing the applications we need the scopes for.\n * @returns A flattened array of scopes.\n */\nfunction getFlattenScopes(apps: OAuthApplications): string[] {\n const admin = apps.adminApi?.scopes ?? []\n const partner = apps.partnersApi?.scopes ?? []\n const storefront = apps.storefrontRendererApi?.scopes ?? []\n const businessPlatform = apps.businessPlatformApi?.scopes ?? []\n const appManagement = apps.appManagementApi?.scopes ?? []\n const requestedScopes = [...admin, ...partner, ...storefront, ...businessPlatform, ...appManagement]\n return allDefaultScopes(requestedScopes)\n}\n\n/**\n * Get the scopes for the given applications.\n *\n * @param apps - An object containing the applications we need the scopes for.\n * @returns An object containing the scopes for each application.\n */\nfunction getExchangeScopes(apps: OAuthApplications): ExchangeScopes {\n const adminScope = apps.adminApi?.scopes ?? []\n const partnerScope = apps.partnersApi?.scopes ?? []\n const storefrontScopes = apps.storefrontRendererApi?.scopes ?? []\n const businessPlatformScopes = apps.businessPlatformApi?.scopes ?? []\n const appManagementScopes = apps.appManagementApi?.scopes ?? []\n return {\n admin: apiScopes('admin', adminScope),\n partners: apiScopes('partners', partnerScope),\n storefront: apiScopes('storefront-renderer', storefrontScopes),\n businessPlatform: apiScopes('business-platform', businessPlatformScopes),\n appManagement: apiScopes('app-management', appManagementScopes),\n }\n}\n\nfunction buildIdentityTokenFromEnv(\n scopes: string[],\n identityTokenInformation: {accessToken: string; refreshToken: string; userId: string},\n) {\n return {\n ...identityTokenInformation,\n expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),\n scopes,\n alias: undefined,\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../../src/private/node/session.ts"],"names":[],"mappings":"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","sourcesContent":["import {applicationId} from './session/identity.js'\nimport {validateSession} from './session/validate.js'\nimport {allDefaultScopes, apiScopes} from './session/scopes.js'\nimport {\n exchangeAccessForApplicationTokens,\n exchangeCustomPartnerToken,\n ExchangeScopes,\n refreshAccessToken,\n InvalidGrantError,\n InvalidRequestError,\n} from './session/exchange.js'\nimport {IdentityToken, Session, Sessions} from './session/schema.js'\nimport * as sessionStore from './session/store.js'\nimport {pollForDeviceAuthorization, requestDeviceAuthorization} from './session/device-authorization.js'\nimport {automationTokenAuthMethod, automationTokenVariablesProblem} from './session/automation-token.js'\nimport {isThemeAccessSession} from './api/rest.js'\nimport {getCurrentSessionId, setCurrentSessionId} from './conf-store.js'\nimport {UserEmailQueryString, UserEmailQuery} from './api/graphql/business-platform-destinations/user-email.js'\nimport {outputContent, outputToken, outputDebug, outputCompleted} from '../../public/node/output.js'\nimport {themeToken} from '../../public/node/context/local.js'\nimport {AbortError} from '../../public/node/error.js'\nimport {normalizeStoreFqdn, identityFqdn} from '../../public/node/context/fqdn.js'\nimport {getIdentityTokenInformation, getAppAutomationToken} from '../../public/node/environment.js'\nimport {AdminSession, ensureNoServiceAccountToken, logout} from '../../public/node/session.js'\nimport {nonRandomUUID} from '../../public/node/crypto.js'\nimport {isEmpty} from '../../public/common/object.js'\nimport {businessPlatformRequest} from '../../public/node/api/business-platform.js'\n\n/**\n * Fetches the user's email from the Business Platform API\n * @param businessPlatformToken - The business platform token\n * @returns The user's email address or undefined if not found\n */\nasync function fetchEmail(businessPlatformToken: string | undefined): Promise<string | undefined> {\n if (!businessPlatformToken) return undefined\n\n try {\n const userEmailResult = await businessPlatformRequest<UserEmailQuery>(UserEmailQueryString, businessPlatformToken)\n return userEmailResult.currentUserAccount?.email ?? undefined\n // eslint-disable-next-line no-catch-all/no-catch-all\n } catch (error) {\n outputDebug(outputContent`Failed to fetch user email: ${(error as Error).message ?? String(error)}`)\n return undefined\n }\n}\n\n/**\n * A scope supported by the Shopify Admin API.\n */\nexport type AdminAPIScope = 'graphql' | 'themes' | 'collaborator'\n\n/**\n * It represents the options to authenticate against the Shopify Admin API.\n */\n\ninterface AdminAPIOAuthOptions {\n /** Store to request permissions for. */\n storeFqdn: string\n /** List of scopes to request permissions for. */\n scopes: AdminAPIScope[]\n}\n\n/**\n * A scope supported by the Partners API.\n */\nexport type PartnersAPIScope = 'cli'\ninterface PartnersAPIOAuthOptions {\n /** List of scopes to request permissions for. */\n scopes: PartnersAPIScope[]\n}\n\n/**\n * A scope supported by the Developer Platform API.\n */\nexport type AppManagementAPIScope = 'https://api.shopify.com/auth/organization.apps.manage'\ninterface AppManagementAPIOauthOptions {\n /** List of scopes to request permissions for. */\n scopes: AppManagementAPIScope[]\n}\n\n/**\n * A scope supported by the Storefront Renderer API.\n */\nexport type StorefrontRendererScope = 'devtools' | 'graphql'\ninterface StorefrontRendererAPIOAuthOptions {\n /** List of scopes to request permissions for. */\n scopes: StorefrontRendererScope[]\n}\n\nexport type BusinessPlatformScope = 'destinations'\ninterface BusinessPlatformAPIOAuthOptions {\n /** List of scopes to request permissions for. */\n scopes: BusinessPlatformScope[]\n}\n\n/**\n * It represents the authentication requirements and\n * is the input necessary to trigger the authentication\n * flow.\n */\nexport interface OAuthApplications {\n adminApi?: AdminAPIOAuthOptions\n storefrontRendererApi?: StorefrontRendererAPIOAuthOptions\n partnersApi?: PartnersAPIOAuthOptions\n businessPlatformApi?: BusinessPlatformAPIOAuthOptions\n appManagementApi?: AppManagementAPIOauthOptions\n}\n\nexport interface OAuthSession {\n admin?: AdminSession\n partners?: string\n storefront?: string\n businessPlatform?: string\n appManagement?: string\n userId: string\n}\n\ntype AuthMethod =\n | 'partners_token'\n | 'service_account_token'\n | 'device_auth'\n | 'theme_access_token'\n | 'custom_app_token'\n | 'none'\n\nlet userId: undefined | string\nlet authMethod: AuthMethod = 'none'\nlet commandSessionId: string | undefined\n\n/**\n * Retrieves a stable user identifier for analytics, or `'unknown'` if none applies.\n *\n * Evaluation order:\n * 1. If an app automation token or theme token is used, returns a deterministic UUID\n * derived from that secret.\n * 2. Otherwise, if `setLastSeenUserIdAfterAuth` was called (e.g. after OAuth), returns that value.\n * 3. Otherwise, if a persisted CLI session id is available, returns it.\n * 4. Otherwise returns `'unknown'`.\n *\n * @returns A Promise that resolves to the user ID as a string.\n */\nexport async function getLastSeenUserIdAfterAuth(): Promise<string> {\n const customToken = getAppAutomationToken() ?? themeToken()\n if (customToken) return nonRandomUUID(customToken)\n\n if (userId) return userId\n\n const currentSessionId = getCurrentSessionId()\n if (currentSessionId) return currentSessionId\n\n return 'unknown'\n}\n\nexport function setLastSeenUserIdAfterAuth(id: string) {\n userId = id\n}\n\n/**\n * Retrieves the last seen authentication method used in the current session.\n *\n * This function checks for the authentication method in the following order:\n * 1. Returns the cached auth method if it's not 'none'.\n * 2. Checks for a cached session, which implies 'device_auth' was used.\n * 3. Checks for an automation token in the environment: `service_account_token` for SHOPIFY_SERVICE_ACCOUNT_TOKEN,\n * `partners_token` for the app and Partner variables.\n * 4. Checks for a theme password in the environment.\n * 5. If none of the above are true, returns 'none'.\n *\n * @returns A Promise that resolves to the last seen authentication method as an AuthMethod type.\n */\nexport async function getLastSeenAuthMethod(): Promise<AuthMethod> {\n if (authMethod !== 'none') return authMethod\n\n if (getCurrentSessionId()) return 'device_auth'\n\n const appAutomationToken = getAppAutomationToken()\n if (appAutomationToken) return automationTokenAuthMethod()\n\n const themePassword = themeToken()\n if (themePassword) {\n return isThemeAccessSession({token: themePassword, storeFqdn: ''}) ? 'theme_access_token' : 'custom_app_token'\n }\n\n return 'none'\n}\n\nexport function setLastSeenAuthMethod(method: AuthMethod) {\n authMethod = method\n}\n\nexport function setCommandSessionId(sessionId: string | undefined) {\n commandSessionId = sessionId\n}\n\nexport interface EnsureAuthenticatedAdditionalOptions {\n noPrompt?: boolean\n forceRefresh?: boolean\n forceNewSession?: boolean\n}\n\n/**\n * This method ensures that we have a valid session to authenticate against the given applications using the provided scopes.\n *\n * @param applications - An object containing the applications we need to be authenticated with.\n * @param _env - Optional environment variables to use.\n * @param options - Optional extra options to use.\n * @returns An instance with the access tokens organized by application.\n */\nexport async function ensureAuthenticated(\n applications: OAuthApplications,\n _env?: NodeJS.ProcessEnv,\n {forceRefresh = false, noPrompt = false, forceNewSession = false}: EnsureAuthenticatedAdditionalOptions = {},\n): Promise<OAuthSession> {\n // Commands that support automation tokens exchange them before calling this function, and getAppAutomationToken\n // returns no token when the variables are invalid, so an invalid environment always reaches this check before any\n // login starts.\n const variablesProblem = automationTokenVariablesProblem()\n if (variablesProblem) throw new AbortError(variablesProblem.message, variablesProblem.tryMessage)\n ensureNoServiceAccountToken()\n\n const fqdn = await identityFqdn()\n\n const previousStoreFqdn = applications.adminApi?.storeFqdn\n if (previousStoreFqdn) {\n const normalizedStoreName = normalizeStoreFqdn(previousStoreFqdn)\n if (previousStoreFqdn === applications.adminApi?.storeFqdn) {\n applications.adminApi.storeFqdn = normalizedStoreName\n }\n }\n\n const sessions = (await sessionStore.fetch()) ?? {}\n\n let currentSessionId = forceNewSession ? undefined : (commandSessionId ?? getCurrentSessionId())\n if (!currentSessionId && !commandSessionId) {\n const userIds = Object.keys(sessions[fqdn] ?? {})\n if (userIds.length > 0) currentSessionId = userIds[0]\n }\n const currentSession: Session | undefined =\n currentSessionId && !forceNewSession ? sessions[fqdn]?.[currentSessionId] : undefined\n const scopes = getFlattenScopes(applications)\n\n outputDebug(outputContent`Validating existing session against the scopes:\n${outputToken.json(scopes)}\nFor applications:\n${outputToken.json(applications)}\n`)\n\n const validationResult = await validateSession(scopes, applications, currentSession)\n\n let newSession = {}\n\n if (validationResult === 'needs_full_auth') {\n await throwOnNoPrompt(noPrompt)\n outputDebug(outputContent`Initiating the full authentication flow...`)\n newSession = await executeCompleteFlow(applications, currentSession?.identity.alias)\n } else if (validationResult === 'needs_refresh' || forceRefresh) {\n outputDebug(outputContent`The current session is valid but needs refresh. Refreshing...`)\n try {\n newSession = await refreshTokens(currentSession!, applications)\n } catch (error) {\n if (error instanceof InvalidGrantError) {\n await throwOnNoPrompt(noPrompt)\n newSession = await executeCompleteFlow(applications, currentSession?.identity.alias)\n } else if (error instanceof InvalidRequestError) {\n await sessionStore.remove()\n throw new AbortError('\\nError validating auth session', \"We've cleared the current session, please try again\")\n } else {\n throw error\n }\n }\n }\n\n const completeSession = {...currentSession, ...newSession} as Session\n const newSessionId = completeSession.identity.userId\n const updatedSessions: Sessions = {\n ...sessions,\n [fqdn]: {...sessions[fqdn], [newSessionId]: completeSession},\n }\n\n // Save the new session info if it has changed\n if (!isEmpty(newSession)) {\n await sessionStore.store(updatedSessions)\n if (!commandSessionId) setCurrentSessionId(newSessionId)\n }\n\n const tokens = await tokensFor(applications, completeSession)\n\n const envToken = getAppAutomationToken()\n if (envToken && applications.partnersApi) {\n tokens.partners = (await exchangeCustomPartnerToken(envToken)).accessToken\n }\n\n setLastSeenAuthMethod(envToken ? 'partners_token' : 'device_auth')\n setLastSeenUserIdAfterAuth(tokens.userId)\n return tokens\n}\n\nasync function throwOnNoPrompt(noPrompt: boolean) {\n if (!noPrompt) return\n await logout()\n throw new AbortError(\n `The currently available CLI credentials are invalid.\n\nThe CLI is currently unable to prompt for reauthentication.`,\n 'Restart the CLI process you were running. If in an interactive terminal, you will be prompted to reauthenticate. If in a non-interactive terminal, ensure the correct credentials are available in the program environment.',\n )\n}\n\n/**\n * Execute the full authentication flow.\n *\n * @param applications - An object containing the applications we need to be authenticated with.\n * @param existingAlias - Optional alias from a previous session to preserve if the email fetch fails.\n */\nasync function executeCompleteFlow(applications: OAuthApplications, existingAlias?: string): Promise<Session> {\n const scopes = getFlattenScopes(applications)\n const exchangeScopes = getExchangeScopes(applications)\n const store = applications.adminApi?.storeFqdn\n\n let identityToken: IdentityToken\n const identityTokenInformation = getIdentityTokenInformation()\n if (identityTokenInformation) {\n identityToken = buildIdentityTokenFromEnv(scopes, identityTokenInformation)\n } else {\n // Request a device code to authorize without a browser redirect.\n outputDebug(outputContent`Requesting device authorization code...`)\n const deviceAuth = await requestDeviceAuthorization(scopes)\n\n // Poll for the identity token\n outputDebug(outputContent`Starting polling for the identity token...`)\n identityToken = await pollForDeviceAuthorization(deviceAuth.deviceCode, deviceAuth.interval)\n }\n\n // Exchange identity token for application tokens\n outputDebug(outputContent`CLI token received. Exchanging it for application tokens...`)\n const result = await exchangeAccessForApplicationTokens(identityToken, exchangeScopes, store)\n\n // Preserve existing alias if available, otherwise try fetching email\n const businessPlatformToken = result[applicationId('business-platform')]?.accessToken\n const alias = existingAlias ?? (await fetchEmail(businessPlatformToken)) ?? identityToken.userId\n\n const session: Session = {\n identity: {\n ...identityToken,\n alias,\n },\n applications: result,\n }\n\n outputCompleted(`Logged in.`)\n\n return session\n}\n\n/**\n * Refresh the tokens for a given session.\n *\n * @param session - The session to refresh.\n */\nasync function refreshTokens(session: Session, applications: OAuthApplications): Promise<Session> {\n // Refresh Identity Token\n const identityToken = await refreshAccessToken(session.identity)\n // Exchange new identity token for application tokens\n const exchangeScopes = getExchangeScopes(applications)\n const applicationTokens = await exchangeAccessForApplicationTokens(\n identityToken,\n exchangeScopes,\n applications.adminApi?.storeFqdn,\n )\n\n return {\n identity: {...identityToken, alias: session.identity.alias},\n applications: applicationTokens,\n }\n}\n\n/**\n * Get the application tokens for a given session.\n *\n * @param applications - An object containing the applications we need the tokens for.\n * @param session - The current session.\n * @param fqdn - The identity FQDN.\n */\nasync function tokensFor(applications: OAuthApplications, session: Session): Promise<OAuthSession> {\n const tokens: OAuthSession = {\n userId: session.identity.userId,\n }\n\n if (applications.adminApi) {\n const appId = applicationId('admin')\n const realAppId = `${applications.adminApi.storeFqdn}-${appId}`\n const token = session.applications[realAppId]?.accessToken\n if (token) {\n tokens.admin = {token, storeFqdn: applications.adminApi.storeFqdn}\n }\n }\n\n if (applications.partnersApi) {\n const appId = applicationId('partners')\n tokens.partners = session.applications[appId]?.accessToken\n }\n\n if (applications.storefrontRendererApi) {\n const appId = applicationId('storefront-renderer')\n tokens.storefront = session.applications[appId]?.accessToken\n }\n\n if (applications.businessPlatformApi) {\n const appId = applicationId('business-platform')\n tokens.businessPlatform = session.applications[appId]?.accessToken\n }\n\n if (applications.appManagementApi) {\n const appId = applicationId('app-management')\n tokens.appManagement = session.applications[appId]?.accessToken\n }\n\n return tokens\n}\n\n// Scope Helpers\n/**\n * Get a flattened array of scopes for the given applications.\n *\n * @param apps - An object containing the applications we need the scopes for.\n * @returns A flattened array of scopes.\n */\nfunction getFlattenScopes(apps: OAuthApplications): string[] {\n const admin = apps.adminApi?.scopes ?? []\n const partner = apps.partnersApi?.scopes ?? []\n const storefront = apps.storefrontRendererApi?.scopes ?? []\n const businessPlatform = apps.businessPlatformApi?.scopes ?? []\n const appManagement = apps.appManagementApi?.scopes ?? []\n const requestedScopes = [...admin, ...partner, ...storefront, ...businessPlatform, ...appManagement]\n return allDefaultScopes(requestedScopes)\n}\n\n/**\n * Get the scopes for the given applications.\n *\n * @param apps - An object containing the applications we need the scopes for.\n * @returns An object containing the scopes for each application.\n */\nfunction getExchangeScopes(apps: OAuthApplications): ExchangeScopes {\n const adminScope = apps.adminApi?.scopes ?? []\n const partnerScope = apps.partnersApi?.scopes ?? []\n const storefrontScopes = apps.storefrontRendererApi?.scopes ?? []\n const businessPlatformScopes = apps.businessPlatformApi?.scopes ?? []\n const appManagementScopes = apps.appManagementApi?.scopes ?? []\n return {\n admin: apiScopes('admin', adminScope),\n partners: apiScopes('partners', partnerScope),\n storefront: apiScopes('storefront-renderer', storefrontScopes),\n businessPlatform: apiScopes('business-platform', businessPlatformScopes),\n appManagement: apiScopes('app-management', appManagementScopes),\n }\n}\n\nfunction buildIdentityTokenFromEnv(\n scopes: string[],\n identityTokenInformation: {accessToken: string; refreshToken: string; userId: string},\n) {\n return {\n ...identityTokenInformation,\n expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),\n scopes,\n alias: undefined,\n }\n}\n"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { AbortError } from '../../../public/node/error.js';
|
|
2
|
+
/**
|
|
3
|
+
* The abort that ends a Trust Battery challenge: the session cannot answer it, or
|
|
4
|
+
* the request was challenged again after the verification.
|
|
5
|
+
*/
|
|
6
|
+
export declare class TrustChallengeError extends AbortError {
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Runs a theme Admin API request and, when Core answers with a Trust Battery
|
|
10
|
+
* challenge, waits for the user to complete it in the browser before retrying once.
|
|
11
|
+
*
|
|
12
|
+
* A request that starts while another request's challenge is still being completed
|
|
13
|
+
* waits for that challenge first, so it is sent once instead of being challenged
|
|
14
|
+
* itself and then retried. A request that was already in flight when a challenge
|
|
15
|
+
* was completed retries without a second prompt, unless another prompt is open:
|
|
16
|
+
* then it waits for that prompt too.
|
|
17
|
+
*
|
|
18
|
+
* @param request - The request to run and possibly retry.
|
|
19
|
+
* @returns The result of the request.
|
|
20
|
+
*/
|
|
21
|
+
export declare function withTrustChallengeRetry<T>(request: () => Promise<T>): Promise<T>;
|
|
22
|
+
/**
|
|
23
|
+
* Extracts the challenge URL from a Trust Battery `CHALLENGE_REQUIRED` GraphQL error.
|
|
24
|
+
*
|
|
25
|
+
* Only URLs on the Admin host are trusted. Anything else is not treated as a
|
|
26
|
+
* challenge and the original error surfaces through normal error handling.
|
|
27
|
+
*
|
|
28
|
+
* @param error - The error thrown by an Admin API request.
|
|
29
|
+
* @returns The challenge URL, or undefined when the error is not a trust challenge.
|
|
30
|
+
*/
|
|
31
|
+
export declare function trustChallengeUrl(error: unknown): Promise<string | undefined>;
|