This repository has been archived by the owner on Apr 11, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 387
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1036 from Shopify/introduce-token-exchange-api
Introduce Token Exchange API
- Loading branch information
Showing
15 changed files
with
611 additions
and
96 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@shopify/shopify-api": minor | ||
--- | ||
|
||
Introduce token exchange API for fetching access tokens. This feature is currently unstable and is hidden behind the `unstable_tokenExchange` future flag. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,43 @@ | ||
import {ConfigInterface} from '../base-types'; | ||
import {FeatureEnabled, FutureFlagOptions} from '../../future/flags'; | ||
|
||
import {begin, callback} from './oauth/oauth'; | ||
import {nonce} from './oauth/nonce'; | ||
import {safeCompare} from './oauth/safe-compare'; | ||
import {getEmbeddedAppUrl, buildEmbeddedAppUrl} from './get-embedded-app-url'; | ||
import {OAuthBegin, OAuthCallback, begin, callback} from './oauth/oauth'; | ||
import {Nonce, nonce} from './oauth/nonce'; | ||
import {SafeCompare, safeCompare} from './oauth/safe-compare'; | ||
import { | ||
getEmbeddedAppUrl, | ||
buildEmbeddedAppUrl, | ||
GetEmbeddedAppUrl, | ||
BuildEmbeddedAppUrl, | ||
} from './get-embedded-app-url'; | ||
import {TokenExchange, tokenExchange} from './oauth/token-exchange'; | ||
|
||
export function shopifyAuth(config: ConfigInterface) { | ||
return { | ||
export function shopifyAuth<Config extends ConfigInterface>( | ||
config: Config, | ||
): ShopifyAuth<Config['future']> { | ||
const shopify = { | ||
begin: begin(config), | ||
callback: callback(config), | ||
nonce, | ||
safeCompare, | ||
getEmbeddedAppUrl: getEmbeddedAppUrl(config), | ||
buildEmbeddedAppUrl: buildEmbeddedAppUrl(config), | ||
}; | ||
} as ShopifyAuth<Config['future']>; | ||
|
||
if (config.future?.unstable_tokenExchange) { | ||
shopify.tokenExchange = tokenExchange(config); | ||
} | ||
|
||
return shopify; | ||
} | ||
|
||
export type ShopifyAuth = ReturnType<typeof shopifyAuth>; | ||
export type ShopifyAuth<Future extends FutureFlagOptions> = { | ||
begin: OAuthBegin; | ||
callback: OAuthCallback; | ||
nonce: Nonce; | ||
safeCompare: SafeCompare; | ||
getEmbeddedAppUrl: GetEmbeddedAppUrl; | ||
buildEmbeddedAppUrl: BuildEmbeddedAppUrl; | ||
} & (FeatureEnabled<Future, 'unstable_tokenExchange'> extends true | ||
? {tokenExchange: TokenExchange} | ||
: {[key: string]: never}); |
141 changes: 141 additions & 0 deletions
141
packages/shopify-api/lib/auth/oauth/__tests__/create-session.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
import {createSession} from '../create-session'; | ||
import {Session, shopifyApi} from '../../..'; | ||
import {testConfig} from '../../../__tests__/test-config'; | ||
|
||
let shop: string; | ||
const STATIC_UUID = 'test-uuid'; | ||
|
||
jest.useFakeTimers().setSystemTime(new Date('2023-11-11')); | ||
|
||
jest.mock('uuid', () => ({v4: jest.fn(() => STATIC_UUID)})); | ||
jest.requireMock('uuid'); | ||
|
||
beforeEach(() => { | ||
shop = 'someshop.myshopify.io'; | ||
}); | ||
|
||
describe('createSession', () => { | ||
describe('when receiving an offline token', () => { | ||
test.each([true, false])( | ||
`creates a new offline session when embedded is %s`, | ||
(isEmbeddedApp) => { | ||
const shopify = shopifyApi(testConfig({isEmbeddedApp})); | ||
|
||
const accessTokenResponse = { | ||
access_token: 'some access token string', | ||
scope: shopify.config.scopes.toString(), | ||
}; | ||
|
||
const session = createSession({ | ||
config: shopify.config, | ||
accessTokenResponse, | ||
shop, | ||
state: 'test-state', | ||
}); | ||
|
||
expect(session).toEqual( | ||
new Session({ | ||
id: `offline_${shop}`, | ||
shop, | ||
isOnline: false, | ||
state: 'test-state', | ||
accessToken: accessTokenResponse.access_token, | ||
scope: accessTokenResponse.scope, | ||
}), | ||
); | ||
}, | ||
); | ||
}); | ||
|
||
describe('when receiving an online token', () => { | ||
test('creates a new online session with shop_user as id when embedded is true', () => { | ||
const shopify = shopifyApi(testConfig({isEmbeddedApp: true})); | ||
|
||
const onlineAccessInfo = { | ||
expires_in: 525600, | ||
associated_user_scope: 'pet_kitties', | ||
associated_user: { | ||
id: 8675309, | ||
first_name: 'John', | ||
last_name: 'Smith', | ||
email: 'john@example.com', | ||
email_verified: true, | ||
account_owner: true, | ||
locale: 'en', | ||
collaborator: true, | ||
}, | ||
}; | ||
|
||
const accessTokenResponse = { | ||
access_token: 'some access token', | ||
scope: 'pet_kitties, walk_dogs', | ||
...onlineAccessInfo, | ||
}; | ||
|
||
const session = createSession({ | ||
config: shopify.config, | ||
accessTokenResponse, | ||
shop, | ||
state: 'test-state', | ||
}); | ||
|
||
expect(session).toEqual( | ||
new Session({ | ||
id: `${shop}_${onlineAccessInfo.associated_user.id}`, | ||
shop, | ||
isOnline: true, | ||
state: 'test-state', | ||
accessToken: accessTokenResponse.access_token, | ||
scope: accessTokenResponse.scope, | ||
expires: new Date(Date.now() + onlineAccessInfo.expires_in * 1000), | ||
onlineAccessInfo, | ||
}), | ||
); | ||
}); | ||
|
||
test('creates a new online session with uuid as id when embedded is false', () => { | ||
const shopify = shopifyApi(testConfig({isEmbeddedApp: false})); | ||
|
||
const onlineAccessInfo = { | ||
expires_in: 525600, | ||
associated_user_scope: 'pet_kitties', | ||
associated_user: { | ||
id: 8675309, | ||
first_name: 'John', | ||
last_name: 'Smith', | ||
email: 'john@example.com', | ||
email_verified: true, | ||
account_owner: true, | ||
locale: 'en', | ||
collaborator: true, | ||
}, | ||
}; | ||
|
||
const accessTokenResponse = { | ||
access_token: 'some access token', | ||
scope: 'pet_kitties, walk_dogs', | ||
...onlineAccessInfo, | ||
}; | ||
|
||
const session = createSession({ | ||
config: shopify.config, | ||
accessTokenResponse, | ||
shop, | ||
state: 'test-state', | ||
}); | ||
|
||
expect(session).toEqual( | ||
new Session({ | ||
id: STATIC_UUID, | ||
shop, | ||
isOnline: true, | ||
state: 'test-state', | ||
accessToken: accessTokenResponse.access_token, | ||
scope: accessTokenResponse.scope, | ||
expires: new Date(Date.now() + onlineAccessInfo.expires_in * 1000), | ||
onlineAccessInfo, | ||
}), | ||
); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.