Skip to content

Commit

Permalink
Merge pull request #201 from Shopify/https-spin
Browse files Browse the repository at this point in the history
Remove the need for passing NODE_TLS_REJECT_UNAUTHORIZED=0 when interacting with Spin environments
  • Loading branch information
pepicrft authored Jul 27, 2022
2 parents 34d785a + 32e5442 commit 09c3538
Show file tree
Hide file tree
Showing 13 changed files with 177 additions and 77 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/shopify-cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ env.RUBY_VERSION }}
- name: Install Bundler for Windows
- name: Install Bundler
run: gem install bundler -v ${{ env.BUNDLER_VERSION }}
- name: Set Node.js
uses: actions/setup-node@master
Expand Down Expand Up @@ -130,6 +130,8 @@ jobs:
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ env.RUBY_VERSION }}
- name: Install Bundler
run: gem install bundler -v ${{ env.BUNDLER_VERSION }}
- name: Set Node.js on ubuntu environments
uses: actions/setup-node@master
with:
Expand Down
41 changes: 19 additions & 22 deletions packages/cli-kit/src/api/admin.test.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
import * as admin from './admin.js'
import {buildHeaders} from './common.js'
import {AdminSession} from '../session.js'
import {test, vi, expect, describe} from 'vitest'
import {request as graphqlRequest} from 'graphql-request'

vi.mock('graphql-request', async () => {
const {gql} = await vi.importActual('graphql-request')
return {
request: vi.fn(),
gql,
}
})
import {graphqlClient} from '../http/graphql.js'
import {test, vi, expect, describe, beforeEach} from 'vitest'
import {GraphQLClient} from 'graphql-request'

vi.mock('../http/graphql.js')
vi.mock('./common.js', async () => {
const common: any = await vi.importActual('./common.js')
const module: any = await vi.importActual('./common.js')
return {
...common,
...module,
buildHeaders: vi.fn(),
}
})

let client: GraphQLClient
beforeEach(() => {
client = {
request: vi.fn(),
} as any
vi.mocked(graphqlClient).mockResolvedValue(client)
})

const mockedResult = {
publicApiVersions: [
{
Expand All @@ -43,37 +45,32 @@ const Session: AdminSession = {token, storeFqdn: 'store'}
describe('admin-api', () => {
test('calls the graphql client twice: get api version and then execute the request', async () => {
// Given
vi.mocked(graphqlRequest).mockResolvedValue(mockedResult)
vi.mocked(client.request).mockResolvedValue(mockedResult)

// When
await admin.request('query', Session, {})

// Then
expect(graphqlRequest).toHaveBeenCalledTimes(2)
expect(client.request).toHaveBeenCalledTimes(2)
})

test('request is called with correct parameters', async () => {
// Given
// eslint-disable-next-line @typescript-eslint/naming-convention
const headers = {'custom-header': token}
vi.mocked(graphqlRequest).mockResolvedValue(mockedResult)
vi.mocked(client.request).mockResolvedValue(mockedResult)
vi.mocked(buildHeaders).mockResolvedValue(headers)

// When
await admin.request('query', Session, {variables: 'variables'})

// Then
expect(graphqlRequest).toHaveBeenLastCalledWith(
'https://store/admin/api/2022-01/graphql.json',
'query',
{variables: 'variables'},
headers,
)
expect(client.request).toHaveBeenLastCalledWith('query', {variables: 'variables'})
})

test('buildHeaders is called with user token', async () => {
// Given
vi.mocked(graphqlRequest).mockResolvedValue(mockedResult)
vi.mocked(client.request).mockResolvedValue(mockedResult)

// When
await admin.request('query', Session, {})
Expand Down
24 changes: 16 additions & 8 deletions packages/cli-kit/src/api/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import {buildHeaders, debugLogRequest, handlingErrors} from './common.js'
import {AdminSession} from '../session.js'
import {debug, content, token as outputToken} from '../output.js'
import {Bug, Abort} from '../error.js'
import {request as graphqlRequest, gql, RequestDocument, Variables} from 'graphql-request'
import {graphqlClient} from '../http/graphql.js'
import {gql, RequestDocument, Variables} from 'graphql-request'

const UnauthorizedAccessError = (store: string) => {
const adminLink = outputToken.link(`URL`, `https://${store}/admin`)
Expand All @@ -25,8 +26,13 @@ export async function request<T>(query: RequestDocument, session: AdminSession,
const version = await fetchApiVersion(session)
const url = adminUrl(session.storeFqdn, version)
const headers = await buildHeaders(session.token)
const client = await graphqlClient({
headers,
url,
service: 'shopify',
})
debugLogRequest(api, query, variables, headers)
const response = await graphqlRequest<T>(url, query, variables, headers)
const response = await client.request<T>(query, variables)
return response
})
}
Expand All @@ -35,16 +41,18 @@ async function fetchApiVersion(session: AdminSession): Promise<string> {
const url = adminUrl(session.storeFqdn, 'unstable')
const query = apiVersionQuery()
const headers = await buildHeaders(session.token)

const client = await graphqlClient({url, headers, service: 'shopify'})
debug(`
Sending Admin GraphQL request to URL ${url} with query:
${query}
`)
const data = await graphqlRequest<{
publicApiVersions: {handle: string; supported: boolean}[]
}>(url, query, {}, headers).catch((err) => {
throw err.response.status === 403 ? UnauthorizedAccessError(session.storeFqdn) : UnknownError()
})
const data = await client
.request<{
publicApiVersions: {handle: string; supported: boolean}[]
}>(query, {})
.catch((err) => {
throw err.response.status === 403 ? UnauthorizedAccessError(session.storeFqdn) : UnknownError()
})

return data.publicApiVersions
.filter((item) => item.supported)
Expand Down
6 changes: 3 additions & 3 deletions packages/cli-kit/src/api/identity.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {identity} from '../environment/fqdn.js'
import {debug} from '../output.js'
import {fetch} from '../http.js'
import {shopifyFetch} from '../http.js'

export async function validateIdentityToken(token: string) {
try {
Expand All @@ -13,7 +13,7 @@ export async function validateIdentityToken(token: string) {
}
debug(`Sending Identity Introspection request to URL: ${instrospectionURL}`)

const response = await fetch(instrospectionURL, options)
const response = await shopifyFetch('shopify', instrospectionURL, options)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const json: any = await response.json()

Expand All @@ -27,7 +27,7 @@ export async function validateIdentityToken(token: string) {
}

async function getInstrospectionEndpoint(): Promise<string> {
const response = await fetch(`https://${await identity()}/.well-known/openid-configuration.json`)
const response = await shopifyFetch('identity', `https://${await identity()}/.well-known/openid-configuration.json`)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const json: any = await response.json()
return json.introspection_endpoint
Expand Down
53 changes: 27 additions & 26 deletions packages/cli-kit/src/api/partners.test.ts
Original file line number Diff line number Diff line change
@@ -1,63 +1,64 @@
import * as partnersApi from './partners.js'
import {buildHeaders} from './common.js'
import {partners} from '../environment/fqdn.js'
import {test, vi, expect, describe, it} from 'vitest'
import {ClientError, request as graphqlRequest} from 'graphql-request'

vi.mock('graphql-request', async () => {
const {gql, ClientError} = await vi.importActual('graphql-request')
return {
request: vi.fn(),
gql,
ClientError,
}
})
import {graphqlClient} from '../http/graphql.js'
import {test, vi, expect, describe, beforeEach} from 'vitest'
import {GraphQLClient, ClientError} from 'graphql-request'

vi.mock('../http/graphql.js')
vi.mock('./common.js', async () => {
const common: any = await vi.importActual('./common.js')
const module: any = await vi.importActual('./common.js')
return {
...common,
...module,
buildHeaders: vi.fn(),
}
})
vi.mock('../environment/fqdn')
vi.mock('../environment/fqdn.js')

const mockedResult = 'OK'
const partnersFQDN = 'partners.shopify.com'
const url = 'https://partners.shopify.com/api/cli/graphql'
const mockedToken = 'token'

let client: GraphQLClient
beforeEach(() => {
client = {
request: vi.fn(),
} as any
vi.mocked(graphqlClient).mockResolvedValue(client)
})

describe('partners-api', () => {
test('calls the graphql client once', async () => {
// Given
vi.mocked(graphqlRequest).mockResolvedValue(mockedResult)
vi.mocked(client.request).mockResolvedValue(mockedResult)
vi.mocked(partners).mockResolvedValue(partnersFQDN)

// When
await partnersApi.request('query', mockedToken, {some: 'variables'})

// Then
expect(graphqlRequest).toHaveBeenCalledOnce()
expect(client.request).toHaveBeenCalledOnce()
})

test('request is called with correct parameters', async () => {
// Given
// eslint-disable-next-line @typescript-eslint/naming-convention
const headers = {'custom-header': mockedToken}
vi.mocked(graphqlRequest).mockResolvedValue(mockedResult)
vi.mocked(buildHeaders).mockResolvedValue(headers)
vi.mocked(client.request).mockResolvedValue(mockedResult)
vi.mocked(client.request).mockResolvedValue(headers)
vi.mocked(partners).mockResolvedValue(partnersFQDN)

// When
await partnersApi.request('query', mockedToken, {variables: 'variables'})

// Then
expect(graphqlRequest).toHaveBeenLastCalledWith(url, 'query', {variables: 'variables'}, headers)
expect(client.request).toHaveBeenLastCalledWith('query', {variables: 'variables'})
})

test('buildHeaders is called with user token', async () => {
// Given
vi.mocked(graphqlRequest).mockResolvedValue(mockedResult)
vi.mocked(client.request).mockResolvedValue(mockedResult)
vi.mocked(partners).mockResolvedValue(partnersFQDN)

// When
Expand All @@ -69,28 +70,28 @@ describe('partners-api', () => {
})

describe('checkIfTokenIsRevoked', () => {
it('returns true if error is 401', async () => {
test('returns true if error is 401', async () => {
const graphQLError = new ClientError({status: 401}, {query: ''})
vi.mocked(graphqlRequest).mockRejectedValueOnce(graphQLError)
vi.mocked(client.request).mockRejectedValueOnce(graphQLError)
vi.mocked(partners).mockResolvedValue(partnersFQDN)

const got = await partnersApi.checkIfTokenIsRevoked(mockedToken)

expect(got).toBe(true)
})

it('returns false if error is not 401', async () => {
test('returns false if error is not 401', async () => {
const graphQLError = new ClientError({status: 404}, {query: ''})
vi.mocked(graphqlRequest).mockRejectedValueOnce(graphQLError)
vi.mocked(client.request).mockRejectedValueOnce(graphQLError)
vi.mocked(partners).mockResolvedValue(partnersFQDN)

const got = await partnersApi.checkIfTokenIsRevoked(mockedToken)

expect(got).toBe(false)
})

it('returns false if there is no error', async () => {
vi.mocked(graphqlRequest).mockResolvedValue(mockedResult)
test('returns false if there is no error', async () => {
vi.mocked(client.request).mockResolvedValue(mockedResult)
vi.mocked(partners).mockResolvedValue(partnersFQDN)

const got = await partnersApi.checkIfTokenIsRevoked(mockedToken)
Expand Down
19 changes: 14 additions & 5 deletions packages/cli-kit/src/api/partners.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {buildHeaders, debugLogRequest, handlingErrors} from './common.js'
import {ScriptServiceProxyQuery} from './graphql/index.js'
import {partners as partnersFqdn} from '../environment/fqdn.js'
import {request as graphqlRequest, Variables, RequestDocument, ClientError, gql} from 'graphql-request'
import {graphqlClient} from '../http/graphql.js'
import {Variables, ClientError, gql, RequestDocument} from 'graphql-request'

export async function request<T>(query: RequestDocument, token: string, variables?: Variables): Promise<T> {
const api = 'Partners'
Expand All @@ -10,7 +11,12 @@ export async function request<T>(query: RequestDocument, token: string, variable
const url = `https://${fqdn}/api/cli/graphql`
const headers = await buildHeaders(token)
debugLogRequest(api, query, variables, headers)
const response = await graphqlRequest<T>(url, query, variables, headers)
const client = await graphqlClient({
headers,
service: 'partners',
url,
})
const response = await client.request<T>(query, variables)
return response
})
}
Expand All @@ -30,13 +36,16 @@ export async function checkIfTokenIsRevoked(token: string): Promise<boolean> {
}
}
`

const fqdn = await partnersFqdn()
const url = `https://${fqdn}/api/cli/graphql`
const headers = await buildHeaders(token)

const client = await graphqlClient({
headers,
url,
service: 'partners',
})
try {
await graphqlRequest(url, query, {}, headers)
await client.request(query, {})
return false
// eslint-disable-next-line no-catch-all/no-catch-all
} catch (error) {
Expand Down
23 changes: 22 additions & 1 deletion packages/cli-kit/src/environment/service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
import {Environment} from '../network/service.js'
import {
partners as partnersEnvironment,
shopify as shopifyEnvironment,
identity as identityEnvironment,
} from './service.js'
import {Environment, Service} from '../network/service.js'
import constants from '../constants.js'

export async function environmentForService(service: Service): Promise<Environment> {
let environment: Environment
switch (service) {
case 'identity':
environment = await identityEnvironment()
break
case 'partners':
environment = await partnersEnvironment()
break
case 'shopify':
environment = await shopifyEnvironment()
break
}
return environment
}

/**
* Given an environment variable that represents the environment to use for a given serve,
* it returns the environment as a enum;
Expand Down
Loading

0 comments on commit 09c3538

Please sign in to comment.