UNPKG

@shopify/shopify-app-remix

Version:

Shopify Remix - to simplify the building of Shopify Apps with Remix

125 lines (122 loc) 4.91 kB
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 '../../../types.mjs'; import '@remix-run/server-runtime'; import { handleClientErrorFactory } from '../helpers/handle-client-error.mjs'; import { triggerAfterAuthHook } from '../helpers/trigger-after-auth-hook.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)) { 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, }); } 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