|
1 | 1 | import {ApplicationToken, IdentityToken} from './schema.js'
|
2 | 2 | import {applicationId, clientId as getIdentityClientId} from './identity.js'
|
| 3 | +import {tokenExchangeScopes} from './scopes.js' |
3 | 4 | import {API} from '../api.js'
|
4 | 5 | import {identityFqdn} from '../../../public/node/context/fqdn.js'
|
5 | 6 | import {shopifyFetch} from '../../../public/node/http.js'
|
6 | 7 | import {err, ok, Result} from '../../../public/node/result.js'
|
7 | 8 | import {AbortError, BugError, ExtendableError} from '../../../public/node/error.js'
|
8 |
| -import {isAppManagementDisabled} from '../../../public/node/context/local.js' |
9 | 9 | import {setLastSeenAuthMethod, setLastSeenUserIdAfterAuth} from '../session.js'
|
10 | 10 | import * as jose from 'jose'
|
11 | 11 | import {nonRandomUUID} from '@shopify/cli-kit/node/crypto'
|
@@ -40,7 +40,7 @@ export async function exchangeAccessForApplicationTokens(
|
40 | 40 | requestAppToken('storefront-renderer', token, scopes.storefront),
|
41 | 41 | requestAppToken('business-platform', token, scopes.businessPlatform),
|
42 | 42 | store ? requestAppToken('admin', token, scopes.admin, store) : {},
|
43 |
| - isAppManagementDisabled() ? {} : requestAppToken('app-management', token, scopes.appManagement), |
| 43 | + requestAppToken('app-management', token, scopes.appManagement), |
44 | 44 | ])
|
45 | 45 |
|
46 | 46 | return {
|
@@ -69,26 +69,67 @@ export async function refreshAccessToken(currentToken: IdentityToken): Promise<I
|
69 | 69 | }
|
70 | 70 |
|
71 | 71 | /**
|
72 |
| - * Given a custom CLI token passed as ENV variable, request a valid partners API token |
73 |
| - * This token does not accept extra scopes, just the cli one. |
74 |
| - * @param token - The CLI token passed as ENV variable |
| 72 | + * Given a custom CLI token passed as ENV variable request a valid API access token |
| 73 | + * @param token - The CLI token passed as ENV variable `SHOPIFY_CLI_PARTNERS_TOKEN` |
| 74 | + * @param apiName - The API to exchange for the access token |
| 75 | + * @param scopes - The scopes to request with the access token |
75 | 76 | * @returns An instance with the application access tokens.
|
76 | 77 | */
|
77 |
| -export async function exchangeCustomPartnerToken(token: string): Promise<{accessToken: string; userId: string}> { |
78 |
| - const appId = applicationId('partners') |
| 78 | +async function exchangeCliTokenForAccessToken( |
| 79 | + apiName: API, |
| 80 | + token: string, |
| 81 | + scopes: string[], |
| 82 | +): Promise<{accessToken: string; userId: string}> { |
| 83 | + const appId = applicationId(apiName) |
79 | 84 | try {
|
80 |
| - const newToken = await requestAppToken('partners', token, ['https://api.shopify.com/auth/partners.app.cli.access']) |
| 85 | + const newToken = await requestAppToken(apiName, token, scopes) |
81 | 86 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
82 | 87 | const accessToken = newToken[appId]!.accessToken
|
83 | 88 | const userId = nonRandomUUID(token)
|
84 | 89 | setLastSeenUserIdAfterAuth(userId)
|
85 | 90 | setLastSeenAuthMethod('partners_token')
|
86 | 91 | return {accessToken, userId}
|
87 | 92 | } catch (error) {
|
88 |
| - throw new AbortError('The custom token provided is invalid.', 'Ensure the token is correct and not expired.') |
| 93 | + const prettyName = apiName.replace(/-/g, ' ').replace(/\b\w/g, (char) => char.toUpperCase()) |
| 94 | + throw new AbortError( |
| 95 | + `The custom token provided can't be used for the ${prettyName} API.`, |
| 96 | + 'Ensure the token is correct and not expired.', |
| 97 | + ) |
89 | 98 | }
|
90 | 99 | }
|
91 | 100 |
|
| 101 | +/** |
| 102 | + * Given a custom CLI token passed as ENV variable, request a valid Partners API token |
| 103 | + * This token does not accept extra scopes, just the cli one. |
| 104 | + * @param token - The CLI token passed as ENV variable `SHOPIFY_CLI_PARTNERS_TOKEN` |
| 105 | + * @returns An instance with the application access tokens. |
| 106 | + */ |
| 107 | +export async function exchangeCustomPartnerToken(token: string): Promise<{accessToken: string; userId: string}> { |
| 108 | + return exchangeCliTokenForAccessToken('partners', token, tokenExchangeScopes('partners')) |
| 109 | +} |
| 110 | + |
| 111 | +/** |
| 112 | + * Given a custom CLI token passed as ENV variable, request a valid App Management API token |
| 113 | + * @param token - The CLI token passed as ENV variable `SHOPIFY_CLI_PARTNERS_TOKEN` |
| 114 | + * @returns An instance with the application access tokens. |
| 115 | + */ |
| 116 | +export async function exchangeCliTokenForAppManagementAccessToken( |
| 117 | + token: string, |
| 118 | +): Promise<{accessToken: string; userId: string}> { |
| 119 | + return exchangeCliTokenForAccessToken('app-management', token, tokenExchangeScopes('app-management')) |
| 120 | +} |
| 121 | + |
| 122 | +/** |
| 123 | + * Given a custom CLI token passed as ENV variable, request a valid Business Platform API token |
| 124 | + * @param token - The CLI token passed as ENV variable `SHOPIFY_CLI_PARTNERS_TOKEN` |
| 125 | + * @returns An instance with the application access tokens. |
| 126 | + */ |
| 127 | +export async function exchangeCliTokenForBusinessPlatformAccessToken( |
| 128 | + token: string, |
| 129 | +): Promise<{accessToken: string; userId: string}> { |
| 130 | + return exchangeCliTokenForAccessToken('business-platform', token, tokenExchangeScopes('business-platform')) |
| 131 | +} |
| 132 | + |
92 | 133 | type IdentityDeviceError = 'authorization_pending' | 'access_denied' | 'expired_token' | 'slow_down' | 'unknown_failure'
|
93 | 134 |
|
94 | 135 | /**
|
@@ -187,6 +228,7 @@ async function tokenRequest(params: {[key: string]: string}): Promise<Result<Tok
|
187 | 228 | const fqdn = await identityFqdn()
|
188 | 229 | const url = new URL(`https://${fqdn}/oauth/token`)
|
189 | 230 | url.search = new URLSearchParams(Object.entries(params)).toString()
|
| 231 | + |
190 | 232 | const res = await shopifyFetch(url.href, {method: 'POST'})
|
191 | 233 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
192 | 234 | const payload: any = await res.json()
|
|
0 commit comments