Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rename decodeSessionToken to decodeIdToken #1576

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ up:
packages:
- .
env:
NODE_ENV: "development"
NODE_ENV: 'development'

commands:
server:
run: pnpm dev
test:
run: pnpm test
5 changes: 5 additions & 0 deletions packages/apps/shopify-api/future/flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ export interface FutureFlags {
* resolves a conflict with the default() method in that class.
*/
customerAddressDefaultFix?: boolean;

/**
* Remove the deprecated `decodeSessionToken` from `shopifySession` and provide only the `decodeIdToken` method.
*/
decodeIdToken?: boolean;
}

/**
Expand Down
4 changes: 2 additions & 2 deletions packages/apps/shopify-api/lib/auth/oauth/token-exchange.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {throwFailedRequest} from '../../clients/common';
import {decodeSessionToken} from '../../session/decode-session-token';
import {decodeIdToken} from '../../session/decode-id-token';
import {sanitizeShop} from '../../utils/shop-validator';
import {ConfigInterface} from '../../base-types';
import {Session} from '../../session/session';
Expand Down Expand Up @@ -34,7 +34,7 @@ export function tokenExchange(config: ConfigInterface): TokenExchange {
sessionToken,
requestedTokenType,
}: TokenExchangeParams) => {
await decodeSessionToken(config)(sessionToken);
await decodeIdToken(config)(sessionToken);

const body = {
client_id: config.apiKey,
Expand Down
9 changes: 6 additions & 3 deletions packages/apps/shopify-api/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export interface Shopify<
config: ConfigInterface<Params>;
clients: ShopifyClients;
auth: ShopifyAuth;
session: ShopifySession;
session: ShopifySession<ConfigInterface<Params>>;
utils: ShopifyUtils;

/**
Expand All @@ -62,7 +62,7 @@ export interface Shopify<
export function shopifyApi<
Params extends ConfigParams<Resources, Future>,
Resources extends ShopifyRestResources,
Future extends FutureFlagOptions,
Future extends FutureFlagOptions = Params['future'],
>({
future,
restResources,
Expand All @@ -79,7 +79,10 @@ export function shopifyApi<
config: validatedConfig,
clients: clientClasses(validatedConfig),
auth: shopifyAuth(validatedConfig),
session: shopifySession(validatedConfig),
// TODO: This doesn't seem right
session: shopifySession(
validatedConfig as ConfigInterface<ConfigParams<any, Future>>,
),
utils: shopifyUtils(validatedConfig),
webhooks: shopifyWebhooks(validatedConfig),
billing: shopifyBilling(validatedConfig),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ describe('JWT session token', () => {
const shopify = shopifyApi(testConfig());
const token = await signJWT(shopify.config.apiSecretKey, payload);

const actualPayload = await shopify.session.decodeSessionToken(token);
const actualPayload = await shopify.session.decodeIdToken(token);
expect(actualPayload).toStrictEqual(payload);
});

test('fails with invalid tokens', async () => {
const shopify = shopifyApi(testConfig());

await expect(
shopify.session.decodeSessionToken('not_a_valid_token'),
shopify.session.decodeIdToken('not_a_valid_token'),
).rejects.toThrow(ShopifyErrors.InvalidJwtError);
});

Expand All @@ -47,7 +47,7 @@ describe('JWT session token', () => {
invalidPayload.exp = new Date().getTime() / 1000 - 60;

const token = await signJWT(shopify.config.apiSecretKey, invalidPayload);
await expect(shopify.session.decodeSessionToken(token)).rejects.toThrow(
await expect(shopify.session.decodeIdToken(token)).rejects.toThrow(
ShopifyErrors.InvalidJwtError,
);
});
Expand All @@ -59,7 +59,7 @@ describe('JWT session token', () => {
invalidPayload.nbf = new Date().getTime() / 1000 + 60;

const token = await signJWT(shopify.config.apiSecretKey, invalidPayload);
await expect(shopify.session.decodeSessionToken(token)).rejects.toThrow(
await expect(shopify.session.decodeIdToken(token)).rejects.toThrow(
ShopifyErrors.InvalidJwtError,
);
});
Expand All @@ -70,7 +70,7 @@ describe('JWT session token', () => {
// The token is signed with a key that is not the current value
const token = await signJWT(shopify.config.apiSecretKey, payload);

await expect(shopify.session.decodeSessionToken(token)).rejects.toThrow(
await expect(shopify.session.decodeIdToken(token)).rejects.toThrow(
ShopifyErrors.InvalidJwtError,
);
});
Expand All @@ -81,7 +81,7 @@ describe('JWT session token', () => {
// The token is signed with a key that is not the current value
const token = await signJWT(shopify.config.apiSecretKey, payload);

const actualPayload = await shopify.session.decodeSessionToken(token, {
const actualPayload = await shopify.session.decodeIdToken(token, {
checkAudience: false,
});
expect(actualPayload).toStrictEqual(payload);
Expand All @@ -96,9 +96,23 @@ describe('JWT session token', () => {
// The token is signed with a key that is not the current value
const token = await signJWT(shopify.config.apiSecretKey, payload);

const actualPayload = await shopify.session.decodeSessionToken(token, {
const actualPayload = await shopify.session.decodeIdToken(token, {
checkAudience: false,
});
expect(actualPayload).toStrictEqual(payload);
});

test('provides legacy decodeSessionToken method when decodeIdToken is not enabled', async () => {
const shopify = shopifyApi(testConfig({future: {decodeIdToken: false}}));

// TODO: Make TypeScript happy
expect(shopify.session.decodeSessionToken).toBeDefined();
});

test('does not provide legacy decodeSessionToken method when decodeIdToken is enabled', async () => {
const shopify = shopifyApi(testConfig({future: {decodeIdToken: true}}));

// @ts-expect-error - decodeSessionToken is not defined in the ShopifySessionInterface
expect(shopify.session.decodeSessionToken).toBeUndefined();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import {JwtPayload} from './types';

const JWT_PERMITTED_CLOCK_TOLERANCE = 10;

export interface DecodeSessionTokenOptions {
export interface DecodeIdTokenOptions {
checkAudience?: boolean;
}

export function decodeSessionToken(config: ConfigInterface) {
export function decodeIdToken(config: ConfigInterface) {
return async (
token: string,
{checkAudience = true}: DecodeSessionTokenOptions = {},
{checkAudience = true}: DecodeIdTokenOptions = {},
): Promise<JwtPayload> => {
let payload: JwtPayload;
try {
Expand Down
62 changes: 56 additions & 6 deletions packages/apps/shopify-api/lib/session/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,71 @@
import {ConfigInterface} from '../base-types';
import {FeatureEnabled, FutureFlagOptions} from 'future/flags';

import {decodeSessionToken} from './decode-session-token';
import {ConfigInterface, ConfigParams} from '../base-types';

import {decodeIdToken} from './decode-id-token';
import {
customAppSession,
getCurrentSessionId,
getJwtSessionId,
getOfflineId,
} from './session-utils';

export function shopifySession(config: ConfigInterface) {
return {
export function shopifySession<
Config extends ConfigInterface<Params>,
Params extends ConfigParams<any, Future>,
Future extends FutureFlagOptions = Config['future'],
>(config: Config): ShopifySession<Config> {
const session:
| ShopifySessionInterface
| ShopifySessionInterfaceWithLegacyDecodeSessionToken = {
customAppSession: customAppSession(config),
getCurrentId: getCurrentSessionId(config),
getOfflineId: getOfflineId(config),
getJwtSessionId: getJwtSessionId(config),
decodeSessionToken: decodeSessionToken(config),
decodeIdToken: decodeIdToken(config),
};

if (usesLegacyDecodeSessionToken(session, config)) {
session.decodeSessionToken = decodeIdToken(config);
}

return session as ShopifySession<Config>;
}

interface SessionConfigArg<Future extends FutureFlagOptions>
extends ConfigInterface {
future: Future;
}

function usesLegacyDecodeSessionToken<
Config extends SessionConfigArg<Future>,
Future extends FutureFlagOptions,
>(
_session:
| ShopifySessionInterface
| ShopifySessionInterfaceWithLegacyDecodeSessionToken,
config: Config,
): _session is ShopifySessionInterfaceWithLegacyDecodeSessionToken {
return !config?.future?.decodeIdToken;
}

interface ShopifySessionInterface {
customAppSession: ReturnType<typeof customAppSession>;
getCurrentId: ReturnType<typeof getCurrentSessionId>;
getOfflineId: ReturnType<typeof getOfflineId>;
getJwtSessionId: ReturnType<typeof getJwtSessionId>;
decodeIdToken: ReturnType<typeof decodeIdToken>;
}

interface ShopifySessionInterfaceWithLegacyDecodeSessionToken
extends ShopifySessionInterface {
/**
* @deprecated Use `decodeIdToken` instead.
*/
decodeSessionToken: ReturnType<typeof decodeIdToken>;
}

export type ShopifySession = ReturnType<typeof shopifySession>;
export type ShopifySession<Config extends ConfigInterface> =
FeatureEnabled<Config['future'], 'decodeIdToken'> extends true
? ShopifySessionInterface
: ShopifySessionInterfaceWithLegacyDecodeSessionToken;
4 changes: 2 additions & 2 deletions packages/apps/shopify-api/lib/session/session-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {sanitizeShop} from '../utils/shop-validator';
import {logger} from '../logger';
import * as ShopifyErrors from '../error';

import {decodeSessionToken} from './decode-session-token';
import {decodeIdToken} from './decode-id-token';
import type {GetCurrentSessionIdParams} from './types';
import {Session} from './session';

Expand Down Expand Up @@ -52,7 +52,7 @@ export function getCurrentSessionId(config: ConfigInterface) {
);
}

const jwtPayload = await decodeSessionToken(config)(matches[1]);
const jwtPayload = await decodeIdToken(config)(matches[1]);
const shop = jwtPayload.dest.replace(/^https:\/\//, '');

log.debug('Found valid JWT payload', {shop, isOnline});
Expand Down
Loading