@shopify/shopify-app-remix
Version:
Shopify Remix - to simplify the building of Shopify Apps with Remix
128 lines (125 loc) • 5.13 kB
JavaScript
import { InvalidJwtError, RequestedTokenType, HttpResponseError } from '@shopify/shopify-api';
import { respondToInvalidSessionToken } from '../../helpers/respond-to-invalid-session-token.mjs';
import { getShopFromRequest } from '../../helpers/get-shop-from-request.mjs';
import { invalidateAccessToken } from '../../helpers/invalidate-access-token.mjs';
import 'isbot';
import '@remix-run/server-runtime';
import { handleClientErrorFactory } from '../helpers/handle-client-error.mjs';
import { triggerAfterAuthHook } from '../helpers/trigger-after-auth-hook.mjs';
import '../../../types.mjs';
import { WITHIN_MILLISECONDS_OF_EXPIRY } from '../../../helpers/ensure-offline-token-is-not-expired.mjs';
class TokenExchangeStrategy {
api;
config;
logger;
constructor({ api, config, logger }) {
this.api = api;
this.config = config;
this.logger = logger;
}
async respondToOAuthRequests(_request) { }
async authenticate(request, sessionContext) {
const { api, config, logger } = this;
const { shop, session, sessionToken } = sessionContext;
if (!sessionToken)
throw new InvalidJwtError();
if (!session ||
!session.isActive(undefined, WITHIN_MILLISECONDS_OF_EXPIRY)) {
logger.info('No valid session found', { shop });
logger.info('Requesting offline access token', { shop });
const { session: offlineSession } = await this.exchangeToken({
request,
sessionToken,
shop,
requestedTokenType: RequestedTokenType.OfflineAccessToken,
});
await config.sessionStorage.storeSession(offlineSession);
let newSession = offlineSession;
if (config.useOnlineTokens) {
logger.info('Requesting online access token', { shop });
const { session: onlineSession } = await this.exchangeToken({
request,
sessionToken,
shop,
requestedTokenType: RequestedTokenType.OnlineAccessToken,
});
await config.sessionStorage.storeSession(onlineSession);
newSession = onlineSession;
}
logger.debug('Request is valid, loaded session from session token', {
shop: newSession.shop,
isOnline: newSession.isOnline,
});
try {
await this.handleAfterAuthHook({ api, config, logger }, newSession, request, sessionToken);
}
catch (errorOrResponse) {
if (errorOrResponse instanceof Response) {
throw errorOrResponse;
}
throw new Response(undefined, {
status: 500,
statusText: 'Internal Server Error',
});
}
return newSession;
}
return session;
}
handleClientError(request) {
const { api, config, logger } = this;
return handleClientErrorFactory({
request,
onError: async ({ session, error }) => {
if (error.response.code === 401) {
logger.debug('Responding to invalid access token', {
shop: getShopFromRequest(request),
});
await invalidateAccessToken({ config, logger }, session);
respondToInvalidSessionToken({
params: { config, api, logger },
request,
});
}
},
});
}
async exchangeToken({ request, shop, sessionToken, requestedTokenType, }) {
const { api, config, logger } = this;
try {
return await api.auth.tokenExchange({
sessionToken,
shop,
requestedTokenType,
expiring: config.future.expiringOfflineAccessTokens,
});
}
catch (error) {
if (error instanceof InvalidJwtError ||
(error instanceof HttpResponseError &&
error.response.code === 400 &&
error.response.body?.error === 'invalid_subject_token')) {
throw respondToInvalidSessionToken({
params: { api, config, logger },
request,
retryRequest: true,
});
}
throw new Response(undefined, {
status: 500,
statusText: 'Internal Server Error',
});
}
}
async handleAfterAuthHook(params, session, request, sessionToken) {
const { config } = params;
await config.idempotentPromiseHandler.handlePromise({
promiseFunction: () => {
return triggerAfterAuthHook(params, session, request, this);
},
identifier: sessionToken,
});
}
}
export { TokenExchangeStrategy };
//# sourceMappingURL=token-exchange.mjs.map