From 591e90e44d88338adcbbf1bd8a57ee86569fbf22 Mon Sep 17 00:00:00 2001 From: Alex Montague Date: Wed, 19 Nov 2025 17:08:26 -0500 Subject: [PATCH] remove shared identity code that is now in client --- .../clients/identity/identity-mock-client.ts | 11 +- .../identity/identity-service-client.ts | 3 + .../cli-kit/src/private/node/session.test.ts | 13 +- packages/cli-kit/src/private/node/session.ts | 17 +- .../node/session/device-authorization.test.ts | 79 +++++++--- .../node/session/device-authorization.ts | 149 ------------------ .../src/private/node/session/exchange.test.ts | 12 +- .../src/private/node/session/exchange.ts | 30 +--- .../src/private/node/session/identity.ts | 69 -------- .../src/private/node/session/validate.test.ts | 9 +- .../src/private/node/session/validate.ts | 11 +- 11 files changed, 102 insertions(+), 301 deletions(-) delete mode 100644 packages/cli-kit/src/private/node/session/identity.ts diff --git a/packages/cli-kit/src/private/node/clients/identity/identity-mock-client.ts b/packages/cli-kit/src/private/node/clients/identity/identity-mock-client.ts index 59ff688919d..ea109f1df67 100644 --- a/packages/cli-kit/src/private/node/clients/identity/identity-mock-client.ts +++ b/packages/cli-kit/src/private/node/clients/identity/identity-mock-client.ts @@ -3,7 +3,6 @@ import {ApplicationToken, IdentityToken} from '../../session/schema.js' import {ExchangeScopes, TokenRequestResult} from '../../session/exchange.js' import {ok, Result} from '../../../../public/node/result.js' import {allDefaultScopes} from '../../session/scopes.js' -import {applicationId} from '../../session/identity.js' export class IdentityMockClient extends IdentityClient { private readonly mockUserId = '08978734-325e-44ce-bc65-34823a8d5180' @@ -28,11 +27,11 @@ export class IdentityMockClient extends IdentityClient { _store?: string, ): Promise<{[x: string]: ApplicationToken}> { return { - [applicationId('app-management')]: this.generateTokens(applicationId('app-management')), - [applicationId('business-platform')]: this.generateTokens(applicationId('business-platform')), - [applicationId('admin')]: this.generateTokens(applicationId('admin')), - [applicationId('partners')]: this.generateTokens(applicationId('partners')), - [applicationId('storefront-renderer')]: this.generateTokens(applicationId('storefront-renderer')), + [this.applicationId('app-management')]: this.generateTokens(this.applicationId('app-management')), + [this.applicationId('business-platform')]: this.generateTokens(this.applicationId('business-platform')), + [this.applicationId('admin')]: this.generateTokens(this.applicationId('admin')), + [this.applicationId('partners')]: this.generateTokens(this.applicationId('partners')), + [this.applicationId('storefront-renderer')]: this.generateTokens(this.applicationId('storefront-renderer')), } } diff --git a/packages/cli-kit/src/private/node/clients/identity/identity-service-client.ts b/packages/cli-kit/src/private/node/clients/identity/identity-service-client.ts index 88d1a520780..b49eb48f3ed 100644 --- a/packages/cli-kit/src/private/node/clients/identity/identity-service-client.ts +++ b/packages/cli-kit/src/private/node/clients/identity/identity-service-client.ts @@ -49,6 +49,9 @@ export class IdentityServiceClient extends IdentityClient { return err({error: payload.error, store: params.store}) } + /** + * Given an expired access token, refresh it to get a new one. + */ async refreshAccessToken(currentToken: IdentityToken): Promise { const clientId = this.clientId() const params = { diff --git a/packages/cli-kit/src/private/node/session.test.ts b/packages/cli-kit/src/private/node/session.test.ts index 533684ac646..e2c9e73ea93 100644 --- a/packages/cli-kit/src/private/node/session.test.ts +++ b/packages/cli-kit/src/private/node/session.test.ts @@ -17,8 +17,6 @@ import {allDefaultScopes} from './session/scopes.js' import {store as storeSessions, fetch as fetchSessions, remove as secureRemove} from './session/store.js' import {ApplicationToken, IdentityToken, Sessions} from './session/schema.js' import {validateSession} from './session/validate.js' -import {applicationId} from './session/identity.js' -import {pollForDeviceAuthorization, requestDeviceAuthorization} from './session/device-authorization.js' import {getCurrentSessionId} from './conf-store.js' import {getIdentityClient} from './clients/identity/instance.js' import {IdentityMockClient} from './clients/identity/identity-mock-client.js' @@ -129,7 +127,6 @@ beforeEach(() => { vi.spyOn(fqdnModule, 'identityFqdn').mockResolvedValue(fqdn) vi.mocked(exchangeAccessForApplicationTokens).mockResolvedValue(appTokens) vi.mocked(refreshAccessToken).mockResolvedValue(validIdentityToken) - vi.mocked(applicationId).mockImplementation((app) => app) vi.mocked(exchangeCustomPartnerToken).mockResolvedValue({ accessToken: partnersToken.accessToken, userId: validIdentityToken.userId, @@ -139,15 +136,6 @@ beforeEach(() => { setLastSeenUserIdAfterAuth(undefined as any) setLastSeenAuthMethod('none') - vi.mocked(requestDeviceAuthorization).mockResolvedValue({ - deviceCode: 'device_code', - userCode: 'user_code', - verificationUri: 'verification_uri', - expiresIn: 3600, - verificationUriComplete: 'verification_uri_complete', - interval: 5, - }) - vi.mocked(pollForDeviceAuthorization).mockResolvedValue(validIdentityToken) vi.mocked(terminalSupportsPrompting).mockReturnValue(true) vi.mocked(businessPlatformRequest).mockResolvedValue({ currentUserAccount: { @@ -156,6 +144,7 @@ beforeEach(() => { }) vi.mocked(getIdentityClient).mockImplementation(() => mockIdentityClient) + vi.spyOn(mockIdentityClient, 'applicationId').mockImplementation((app) => app) vi.spyOn(mockIdentityClient, 'refreshAccessToken').mockResolvedValue(validIdentityToken) vi.spyOn(mockIdentityClient, 'requestAccessToken').mockResolvedValue(validIdentityToken) }) diff --git a/packages/cli-kit/src/private/node/session.ts b/packages/cli-kit/src/private/node/session.ts index 9b66873d3b4..8afb08f5efe 100644 --- a/packages/cli-kit/src/private/node/session.ts +++ b/packages/cli-kit/src/private/node/session.ts @@ -1,4 +1,3 @@ -import {applicationId} from './session/identity.js' import {validateSession} from './session/validate.js' import {allDefaultScopes, apiScopes} from './session/scopes.js' import { @@ -299,13 +298,14 @@ async function executeCompleteFlow(applications: OAuthApplications): Promise { vi.mocked(isTTY).mockReturnValue(true) vi.mocked(isCI).mockReturnValue(false) + vi.mocked(isCloudEnvironment).mockReturnValue(false) + vi.mocked(keypress).mockResolvedValue(undefined) + vi.mocked(openURL).mockResolvedValue(true) + vi.mocked(getIdentityClient).mockImplementation(() => mockIdentityClient) + // Mock stringifyMessage to pass through strings for error messages + vi.mocked(stringifyMessage).mockImplementation((msg) => (typeof msg === 'string' ? msg : String(msg))) }) describe('requestDeviceAuthorization', () => { @@ -32,7 +44,7 @@ describe('requestDeviceAuthorization', () => { verification_uri: 'verification_uri', expires_in: 3600, verification_uri_complete: 'verification_uri_complete', - interval: 5, + interval: 0.05, } const dataExpected: DeviceAuthorizationResponse = { @@ -46,20 +58,31 @@ describe('requestDeviceAuthorization', () => { test('requests an authorization code to initiate the device auth', async () => { // Given - const response = new Response(JSON.stringify(data)) - vi.mocked(shopifyFetch).mockResolvedValue(response) + const deviceAuthResponse = new Response(JSON.stringify(data)) + vi.mocked(shopifyFetch).mockResolvedValueOnce(deviceAuthResponse) vi.mocked(identityFqdn).mockResolvedValue('fqdn.com') + // Mock the token exchange to complete the flow + const identityToken: IdentityToken = { + accessToken: 'access_token', + refreshToken: 'refresh_token', + expiresAt: new Date(2022, 1, 1, 11), + scopes: ['scope1', 'scope2'], + userId: '1234-5678', + alias: '1234-5678', + } + vi.mocked(exchangeDeviceCodeForAccessToken).mockResolvedValue(ok(identityToken)) + // When - const got = await requestDeviceAuthorization(['scope1', 'scope2']) + const got = await mockIdentityClient.requestAccessToken(['scope1', 'scope2']) // Then - expect(shopifyFetch).toBeCalledWith('https://fqdn.com/oauth/device_authorization', { + expect(shopifyFetch).toHaveBeenCalledWith('https://fqdn.com/oauth/device_authorization', { method: 'POST', headers: {'Content-type': 'application/x-www-form-urlencoded'}, body: 'client_id=fbdb2649-e327-4907-8f67-908d24cfd7e3&scope=scope1 scope2', }) - expect(got).toEqual(dataExpected) + expect(got).toEqual(identityToken) }) test('when the response is not valid JSON, throw an error with context', async () => { @@ -71,7 +94,7 @@ describe('requestDeviceAuthorization', () => { vi.mocked(identityFqdn).mockResolvedValue('fqdn.com') // When/Then - await expect(requestDeviceAuthorization(['scope1', 'scope2'])).rejects.toThrowError( + await expect(mockIdentityClient.requestAccessToken(['scope1', 'scope2'])).rejects.toThrowError( 'Received invalid response from authorization service (HTTP 200). Response could not be parsed as valid JSON. If this issue persists, please contact support at https://help.shopify.com', ) }) @@ -85,7 +108,7 @@ describe('requestDeviceAuthorization', () => { vi.mocked(identityFqdn).mockResolvedValue('fqdn.com') // When/Then - await expect(requestDeviceAuthorization(['scope1', 'scope2'])).rejects.toThrowError( + await expect(mockIdentityClient.requestAccessToken(['scope1', 'scope2'])).rejects.toThrowError( 'Received invalid response from authorization service (HTTP 200). Received empty response body. If this issue persists, please contact support at https://help.shopify.com', ) }) @@ -100,7 +123,7 @@ describe('requestDeviceAuthorization', () => { vi.mocked(identityFqdn).mockResolvedValue('fqdn.com') // When/Then - await expect(requestDeviceAuthorization(['scope1', 'scope2'])).rejects.toThrowError( + await expect(mockIdentityClient.requestAccessToken(['scope1', 'scope2'])).rejects.toThrowError( 'Received invalid response from authorization service (HTTP 404). The request may be malformed or unauthorized. Received HTML instead of JSON - the service endpoint may have changed. If this issue persists, please contact support at https://help.shopify.com', ) }) @@ -114,7 +137,7 @@ describe('requestDeviceAuthorization', () => { vi.mocked(identityFqdn).mockResolvedValue('fqdn.com') // When/Then - await expect(requestDeviceAuthorization(['scope1', 'scope2'])).rejects.toThrowError( + await expect(mockIdentityClient.requestAccessToken(['scope1', 'scope2'])).rejects.toThrowError( 'Received invalid response from authorization service (HTTP 500). The service may be experiencing issues. Response could not be parsed as valid JSON. If this issue persists, please contact support at https://help.shopify.com', ) }) @@ -130,13 +153,23 @@ describe('requestDeviceAuthorization', () => { vi.mocked(identityFqdn).mockResolvedValue('fqdn.com') // When/Then - await expect(requestDeviceAuthorization(['scope1', 'scope2'])).rejects.toThrowError( + await expect(mockIdentityClient.requestAccessToken(['scope1', 'scope2'])).rejects.toThrowError( 'Failed to read response from authorization service (HTTP 200). Network or streaming error occurred.', ) }) }) describe('pollForDeviceAuthorization', () => { + const data: any = { + device_code: 'device_code', + user_code: 'user_code', + verification_uri: 'verification_uri', + expires_in: 3600, + verification_uri_complete: 'verification_uri_complete', + // Short interval for testing + interval: 0.05, + } + const identityToken: IdentityToken = { accessToken: 'access_token', refreshToken: 'refresh_token', @@ -148,13 +181,16 @@ describe('pollForDeviceAuthorization', () => { test('poll until a valid token is received', async () => { // Given + const deviceAuthResponse = new Response(JSON.stringify(data)) + vi.mocked(shopifyFetch).mockResolvedValueOnce(deviceAuthResponse) + vi.mocked(identityFqdn).mockResolvedValue('fqdn.com') vi.mocked(exchangeDeviceCodeForAccessToken).mockResolvedValueOnce(err('authorization_pending')) vi.mocked(exchangeDeviceCodeForAccessToken).mockResolvedValueOnce(err('authorization_pending')) vi.mocked(exchangeDeviceCodeForAccessToken).mockResolvedValueOnce(err('authorization_pending')) vi.mocked(exchangeDeviceCodeForAccessToken).mockResolvedValueOnce(ok(identityToken)) // When - const got = await pollForDeviceAuthorization('device_code', 0.05) + const got = await mockIdentityClient.requestAccessToken(['scope1', 'scope2']) // Then expect(exchangeDeviceCodeForAccessToken).toBeCalledTimes(4) @@ -163,12 +199,15 @@ describe('pollForDeviceAuthorization', () => { test('when polling, if an error is received, stop polling and throw error', async () => { // Given + const deviceAuthResponse = new Response(JSON.stringify(data)) + vi.mocked(shopifyFetch).mockResolvedValueOnce(deviceAuthResponse) + vi.mocked(identityFqdn).mockResolvedValue('fqdn.com') vi.mocked(exchangeDeviceCodeForAccessToken).mockResolvedValueOnce(err('authorization_pending')) vi.mocked(exchangeDeviceCodeForAccessToken).mockResolvedValueOnce(err('authorization_pending')) vi.mocked(exchangeDeviceCodeForAccessToken).mockResolvedValueOnce(err('access_denied')) // When - const got = pollForDeviceAuthorization('device_code', 0.05) + const got = mockIdentityClient.requestAccessToken(['scope1', 'scope2']) // Then await expect(got).rejects.toThrow() diff --git a/packages/cli-kit/src/private/node/session/device-authorization.ts b/packages/cli-kit/src/private/node/session/device-authorization.ts index 7353862920a..85ec5f8ec3f 100644 --- a/packages/cli-kit/src/private/node/session/device-authorization.ts +++ b/packages/cli-kit/src/private/node/session/device-authorization.ts @@ -1,13 +1,3 @@ -import {exchangeDeviceCodeForAccessToken} from './exchange.js' -import {IdentityToken} from './schema.js' -import {identityFqdn} from '../../../public/node/context/fqdn.js' -import {shopifyFetch} from '../../../public/node/http.js' -import {outputContent, outputDebug, outputInfo, outputToken} from '../../../public/node/output.js' -import {AbortError, BugError} from '../../../public/node/error.js' -import {isCloudEnvironment} from '../../../public/node/context/local.js' -import {isCI, openURL} from '../../../public/node/system.js' -import {isTTY, keypress} from '../../../public/node/ui.js' -import {getIdentityClient} from '../clients/identity/instance.js' import {Response} from 'node-fetch' export interface DeviceAuthorizationResponse { @@ -19,145 +9,6 @@ export interface DeviceAuthorizationResponse { interval?: number } -/** - * Initiate a device authorization flow. - * This will return a DeviceAuthorizationResponse containing the URL where user - * should go to authorize the device without the need of a callback to the CLI. - * - * Also returns a `deviceCode` used for polling the token endpoint in the next step. - * - * @param scopes - The scopes to request - * @returns An object with the device authorization response. - */ -export async function requestDeviceAuthorization(scopes: string[]): Promise { - const fqdn = await identityFqdn() - const identityClientId = getIdentityClient().clientId() - const queryParams = {client_id: identityClientId, scope: scopes.join(' ')} - const url = `https://${fqdn}/oauth/device_authorization` - - const response = await shopifyFetch(url, { - method: 'POST', - headers: {'Content-type': 'application/x-www-form-urlencoded'}, - body: convertRequestToParams(queryParams), - }) - - // First read the response body as text so we have it for debugging - let responseText: string - try { - responseText = await response.text() - } catch (error) { - throw new BugError( - `Failed to read response from authorization service (HTTP ${response.status}). Network or streaming error occurred.`, - 'Check your network connection and try again.', - ) - } - - // Now try to parse the text as JSON - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let jsonResult: any - try { - jsonResult = JSON.parse(responseText) - } catch { - // JSON.parse failed, handle the parsing error - const errorMessage = buildAuthorizationParseErrorMessage(response, responseText) - throw new BugError(errorMessage) - } - - outputDebug(outputContent`Received device authorization code: ${outputToken.json(jsonResult)}`) - if (!jsonResult.device_code || !jsonResult.verification_uri_complete) { - throw new BugError('Failed to start authorization process') - } - - outputInfo('\nTo run this command, log in to Shopify.') - - if (isCI()) { - throw new AbortError( - 'Authorization is required to continue, but the current environment does not support interactive prompts.', - 'To resolve this, specify credentials in your environment, or run the command in an interactive environment such as your local terminal.', - ) - } - - outputInfo(outputContent`User verification code: ${jsonResult.user_code}`) - const linkToken = outputToken.link(jsonResult.verification_uri_complete) - - const cloudMessage = () => { - outputInfo(outputContent`👉 Open this link to start the auth process: ${linkToken}`) - } - - if (isCloudEnvironment() || !isTTY()) { - cloudMessage() - } else { - outputInfo('👉 Press any key to open the login page on your browser') - await keypress() - const opened = await openURL(jsonResult.verification_uri_complete) - if (opened) { - outputInfo(outputContent`Opened link to start the auth process: ${linkToken}`) - } else { - cloudMessage() - } - } - - return { - deviceCode: jsonResult.device_code, - userCode: jsonResult.user_code, - verificationUri: jsonResult.verification_uri, - expiresIn: jsonResult.expires_in, - verificationUriComplete: jsonResult.verification_uri_complete, - interval: jsonResult.interval, - } -} - -/** - * Poll the Oauth token endpoint with the device code obtained from a DeviceAuthorizationResponse. - * The endpoint will return `authorization_pending` until the user completes the auth flow in the browser. - * Once the user completes the auth flow, the endpoint will return the identity token. - * - * Timeout for the polling is defined by the server and is around 600 seconds. - * - * @param code - The device code obtained after starting a device identity flow - * @param interval - The interval to poll the token endpoint - * @returns The identity token - */ -export async function pollForDeviceAuthorization(code: string, interval = 5): Promise { - let currentIntervalInSeconds = interval - - return new Promise((resolve, reject) => { - const onPoll = async () => { - const result = await exchangeDeviceCodeForAccessToken(code) - if (!result.isErr()) { - resolve(result.value) - return - } - - const error = result.error ?? 'unknown_failure' - - outputDebug(outputContent`Polling for device authorization... status: ${error}`) - switch (error) { - case 'authorization_pending': { - startPolling() - return - } - case 'slow_down': - currentIntervalInSeconds += 5 - startPolling() - return - case 'access_denied': - case 'expired_token': - case 'unknown_failure': { - reject(new Error(`Device authorization failed: ${error}`)) - } - } - } - - const startPolling = () => { - // eslint-disable-next-line @typescript-eslint/no-misused-promises - setTimeout(onPoll, currentIntervalInSeconds * 1000) - } - - startPolling() - }) -} - export function convertRequestToParams(queryParams: {client_id: string; scope: string}): string { return Object.entries(queryParams) .map(([key, value]) => value && `${key}=${value}`) diff --git a/packages/cli-kit/src/private/node/session/exchange.test.ts b/packages/cli-kit/src/private/node/session/exchange.test.ts index f900d3e14f5..df0bd50fcaa 100644 --- a/packages/cli-kit/src/private/node/session/exchange.test.ts +++ b/packages/cli-kit/src/private/node/session/exchange.test.ts @@ -8,12 +8,13 @@ import { refreshAccessToken, requestAppToken, } from './exchange.js' -import {applicationId} from './identity.js' import {IdentityToken} from './schema.js' import {shopifyFetch} from '../../../public/node/http.js' import {identityFqdn} from '../../../public/node/context/fqdn.js' import {getLastSeenUserIdAfterAuth, getLastSeenAuthMethod} from '../session.js' import {AbortError} from '../../../public/node/error.js' +import {getIdentityClient} from '../clients/identity/instance.js' +import {IdentityServiceClient} from '../clients/identity/identity-service-client.js' import {describe, test, expect, vi, afterAll, beforeEach} from 'vitest' import {Response} from 'node-fetch' @@ -25,7 +26,6 @@ const data: any = { refresh_token: 'refresh_token', scope: 'scope scope2', expires_in: 3600, - // id_token:{sub: '1234-5678'} id_token: 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0LTU2NzgifQ.L8IiNHncR4xe42f1fLQZFD5D_HBo7oMlfop2FS-NUCU', } @@ -38,14 +38,18 @@ const identityToken: IdentityToken = { alias: '1234-5678', } +// use real client since we stub out network requests in this "integration" test +const mockIdentityClient = new IdentityServiceClient() + vi.mock('../../../public/node/http.js') vi.mock('../../../public/node/context/fqdn.js') -vi.mock('./identity') +vi.mock('../clients/identity/instance.js') beforeEach(() => { vi.setSystemTime(currentDate) - vi.mocked(applicationId).mockImplementation((api) => api) vi.mocked(identityFqdn).mockResolvedValue('fqdn.com') + vi.mocked(getIdentityClient).mockImplementation(() => mockIdentityClient) + vi.spyOn(mockIdentityClient, 'applicationId').mockImplementation((api) => api) }) afterAll(() => { diff --git a/packages/cli-kit/src/private/node/session/exchange.ts b/packages/cli-kit/src/private/node/session/exchange.ts index 7d815bea09e..395cc11f5df 100644 --- a/packages/cli-kit/src/private/node/session/exchange.ts +++ b/packages/cli-kit/src/private/node/session/exchange.ts @@ -1,9 +1,6 @@ import {ApplicationToken, IdentityToken} from './schema.js' -import {applicationId} from './identity.js' import {tokenExchangeScopes} from './scopes.js' import {API} from '../api.js' -import {identityFqdn} from '../../../public/node/context/fqdn.js' -import {shopifyFetch} from '../../../public/node/http.js' import {err, ok, Result} from '../../../public/node/result.js' import {AbortError, BugError, ExtendableError} from '../../../public/node/error.js' import {setLastSeenAuthMethod, setLastSeenUserIdAfterAuth} from '../session.js' @@ -53,9 +50,6 @@ export async function exchangeAccessForApplicationTokens( } } -/** - * Given an expired access token, refresh it to get a new one. - */ export async function refreshAccessToken(currentToken: IdentityToken): Promise { const clientId = getIdentityClient().clientId() const params = { @@ -81,7 +75,7 @@ async function exchangeCliTokenForAccessToken( token: string, scopes: string[], ): Promise<{accessToken: string; userId: string}> { - const appId = applicationId(apiName) + const appId = getIdentityClient().applicationId(apiName) try { const newToken = await requestAppToken(apiName, token, scopes) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion @@ -164,14 +158,14 @@ export async function requestAppToken( scopes: string[] = [], store?: string, ): Promise<{[x: string]: ApplicationToken}> { - const appId = applicationId(api) - const clientId = getIdentityClient().clientId() + const identityClient = getIdentityClient() + const appId = identityClient.applicationId(api) const params = { grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange', requested_token_type: 'urn:ietf:params:oauth:token-type:access_token', subject_token_type: 'urn:ietf:params:oauth:token-type:access_token', - client_id: clientId, + client_id: identityClient.clientId(), audience: appId, scope: scopes.join(' '), subject_token: token, @@ -222,22 +216,6 @@ export function tokenRequestErrorHandler({error, store}: {error: string; store?: return new AbortError(error) } -async function _tokenRequest(params: { - [key: string]: string -}): Promise> { - const fqdn = await identityFqdn() - const url = new URL(`https://${fqdn}/oauth/token`) - url.search = new URLSearchParams(Object.entries(params)).toString() - - const res = await shopifyFetch(url.href, {method: 'POST'}) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const payload: any = await res.json() - - if (res.ok) return ok(payload) - - return err({error: payload.error, store: params.store}) -} - export function buildIdentityToken( result: TokenRequestResult, existingUserId?: string, diff --git a/packages/cli-kit/src/private/node/session/identity.ts b/packages/cli-kit/src/private/node/session/identity.ts deleted file mode 100644 index 81a8efd4049..00000000000 --- a/packages/cli-kit/src/private/node/session/identity.ts +++ /dev/null @@ -1,69 +0,0 @@ -import {API} from '../api.js' -import {BugError} from '../../../public/node/error.js' -import {Environment, serviceEnvironment} from '../context/service.js' - -function _clientId(): string { - const environment = serviceEnvironment() - if (environment === Environment.Local) { - return 'e5380e02-312a-7408-5718-e07017e9cf52' - } else if (environment === Environment.Production) { - return 'fbdb2649-e327-4907-8f67-908d24cfd7e3' - } else { - return 'e5380e02-312a-7408-5718-e07017e9cf52' - } -} - -export function applicationId(api: API): string { - switch (api) { - case 'admin': { - const environment = serviceEnvironment() - if (environment === Environment.Local) { - return 'e92482cebb9bfb9fb5a0199cc770fde3de6c8d16b798ee73e36c9d815e070e52' - } else if (environment === Environment.Production) { - return '7ee65a63608843c577db8b23c4d7316ea0a01bd2f7594f8a9c06ea668c1b775c' - } else { - return 'e92482cebb9bfb9fb5a0199cc770fde3de6c8d16b798ee73e36c9d815e070e52' - } - } - case 'partners': { - const environment = serviceEnvironment() - if (environment === Environment.Local) { - return 'df89d73339ac3c6c5f0a98d9ca93260763e384d51d6038da129889c308973978' - } else if (environment === Environment.Production) { - return '271e16d403dfa18082ffb3d197bd2b5f4479c3fc32736d69296829cbb28d41a6' - } else { - return 'df89d73339ac3c6c5f0a98d9ca93260763e384d51d6038da129889c308973978' - } - } - case 'storefront-renderer': { - const environment = serviceEnvironment() - if (environment === Environment.Local) { - return '46f603de-894f-488d-9471-5b721280ff49' - } else if (environment === Environment.Production) { - return 'ee139b3d-5861-4d45-b387-1bc3ada7811c' - } else { - return '46f603de-894f-488d-9471-5b721280ff49' - } - } - case 'business-platform': { - const environment = serviceEnvironment() - if (environment === Environment.Local) { - return 'ace6dc89-b526-456d-a942-4b8ef6acda4b' - } else if (environment === Environment.Production) { - return '32ff8ee5-82b8-4d93-9f8a-c6997cefb7dc' - } else { - return 'ace6dc89-b526-456d-a942-4b8ef6acda4b' - } - } - case 'app-management': { - const environment = serviceEnvironment() - if (environment === Environment.Production) { - return '7ee65a63608843c577db8b23c4d7316ea0a01bd2f7594f8a9c06ea668c1b775c' - } else { - return 'e92482cebb9bfb9fb5a0199cc770fde3de6c8d16b798ee73e36c9d815e070e52' - } - } - default: - throw new BugError(`Application id for API of type: ${api}`) - } -} diff --git a/packages/cli-kit/src/private/node/session/validate.test.ts b/packages/cli-kit/src/private/node/session/validate.test.ts index 12f8300b683..e8545c185bf 100644 --- a/packages/cli-kit/src/private/node/session/validate.test.ts +++ b/packages/cli-kit/src/private/node/session/validate.test.ts @@ -1,7 +1,8 @@ import {validateSession} from './validate.js' -import {applicationId} from './identity.js' import {IdentityToken, validateCachedIdentityTokenStructure} from './schema.js' import {OAuthApplications} from '../session.js' +import {getIdentityClient} from '../clients/identity/instance.js' +import {IdentityMockClient} from '../clients/identity/identity-mock-client.js' import {expect, describe, test, vi, afterAll, beforeEach} from 'vitest' const pastDate = new Date(2022, 1, 1, 9) @@ -70,14 +71,18 @@ const defaultApps: OAuthApplications = { storefrontRendererApi: {scopes: []}, } +const mockIdentityClient = new IdentityMockClient() + vi.mock('./identity-token-validation') vi.mock('./identity') vi.mock('./schema') +vi.mock('../clients/identity/instance.js') beforeEach(() => { - vi.mocked(applicationId).mockImplementation((id: any) => id) vi.mocked(validateCachedIdentityTokenStructure).mockReturnValue(true) vi.setSystemTime(currentDate) + vi.mocked(getIdentityClient).mockImplementation(() => mockIdentityClient) + vi.spyOn(mockIdentityClient, 'applicationId').mockImplementation((api) => api) }) afterAll(() => { diff --git a/packages/cli-kit/src/private/node/session/validate.ts b/packages/cli-kit/src/private/node/session/validate.ts index 2dd97f2480e..af80813e1db 100644 --- a/packages/cli-kit/src/private/node/session/validate.ts +++ b/packages/cli-kit/src/private/node/session/validate.ts @@ -1,10 +1,10 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ -import {applicationId} from './identity.js' import {ApplicationToken, IdentityToken, Session, validateCachedIdentityTokenStructure} from './schema.js' import {sessionConstants} from '../constants.js' import {firstPartyDev} from '../../../public/node/context/local.js' import {OAuthApplications} from '../session.js' import {outputDebug} from '../../../public/node/output.js' +import {getIdentityClient} from '../clients/identity/instance.js' type ValidationResult = 'needs_refresh' | 'needs_full_auth' | 'ok' @@ -33,27 +33,28 @@ export async function validateSession( const scopesAreValid = validateScopes(scopes, session.identity) if (!scopesAreValid) return 'needs_full_auth' let tokensAreExpired = isTokenExpired(session.identity) + const identityClient = getIdentityClient() if (applications.partnersApi) { - const appId = applicationId('partners') + const appId = identityClient.applicationId('partners') const token = session.applications[appId]! tokensAreExpired = tokensAreExpired || isTokenExpired(token) } if (applications.appManagementApi) { - const appId = applicationId('app-management') + const appId = identityClient.applicationId('app-management') const token = session.applications[appId]! tokensAreExpired = tokensAreExpired || isTokenExpired(token) } if (applications.storefrontRendererApi) { - const appId = applicationId('storefront-renderer') + const appId = identityClient.applicationId('storefront-renderer') const token = session.applications[appId]! tokensAreExpired = tokensAreExpired || isTokenExpired(token) } if (applications.adminApi) { - const appId = applicationId('admin') + const appId = identityClient.applicationId('admin') const realAppId = `${applications.adminApi.storeFqdn}-${appId}` const token = session.applications[realAppId]! tokensAreExpired = tokensAreExpired || isTokenExpired(token)