diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/authenticate.ts b/packages/shopify-app-remix/src/server/authenticate/admin/authenticate.ts index bd12e1066a..f55ef699ca 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/authenticate.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/authenticate.ts @@ -110,22 +110,22 @@ export class AuthStrategy< const params: BasicParams = {api, logger, config}; logger.info('Authenticating admin request'); - const strategy = new AuthCodeFlowStrategy(); + const strategy = new AuthCodeFlowStrategy(params); - await strategy.handleRoutes(request, params); + await strategy.handleRoutes(request); const sessionTokenHeader = getSessionTokenHeader(request); if (!sessionTokenHeader) { await this.validateUrlParams(request); - await strategy.ensureInstalledOnShop(request, params); + await strategy.ensureInstalledOnShop(request); await this.ensureAppIsEmbeddedIfRequired(request); await this.ensureSessionTokenSearchParamIfRequired(request); } const {sessionContext, shop} = await this.getAuthenticatedSession(request); - return strategy.manageAccessToken(sessionContext, shop, request, params); + return strategy.manageAccessToken(sessionContext, shop, request); } private async validateUrlParams(request: Request) { diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/auth-code-flow.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/auth-code-flow.ts index 64980684d9..8f1efe6060 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/auth-code-flow.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/auth-code-flow.ts @@ -5,6 +5,7 @@ import { InvalidHmacError, InvalidOAuthError, Session, + Shopify, ShopifyRestResources, } from '@shopify/shopify-api'; @@ -18,23 +19,33 @@ import { triggerAfterAuthHook, } from '../helpers'; import {SessionContext} from '../types'; +import {AppConfig} from '../../../config-types'; export class AuthCodeFlowStrategy< Resources extends ShopifyRestResources = ShopifyRestResources, > { - async handleRoutes(request: Request, params: BasicParams) { - await this.handleBouncePageRoute(request, params); - await this.handleExitIframeRoute(request, params); - await this.handleOAuthRoutes(request, params); + protected api: Shopify; + protected config: AppConfig; + protected logger: Shopify['logger']; + + public constructor({api, config, logger}: BasicParams) { + this.api = api; + this.config = config; + this.logger = logger; + } + + async handleRoutes(request: Request) { + await this.handleBouncePageRoute(request); + await this.handleExitIframeRoute(request); + await this.handleOAuthRoutes(request); } async manageAccessToken( sessionContext: SessionContext | undefined, shop: string, request: Request, - params: BasicParams, ): Promise { - const {config, logger} = params; + const {config, logger, api} = this; if ( !sessionContext?.session || !sessionContext?.session.isActive(config.scopes) @@ -43,14 +54,14 @@ export class AuthCodeFlowStrategy< ? 'Found a session, but it has expired, redirecting to OAuth' : 'No session found, redirecting to OAuth'; logger.debug(debugMessage, {shop}); - await redirectToAuthPage(params, request, shop); + await redirectToAuthPage({config, logger, api}, request, shop); } return sessionContext!; } - async ensureInstalledOnShop(request: Request, params: BasicParams) { - const {api, config, logger} = params; + async ensureInstalledOnShop(request: Request) { + const {api, config, logger} = this; const url = new URL(request.url); let shop = url.searchParams.get('shop'); @@ -58,7 +69,7 @@ export class AuthCodeFlowStrategy< // Ensure app is installed logger.debug('Ensuring app is installed on shop', {shop}); - if (!(await this.hasValidOfflineId(request, params))) { + if (!(await this.hasValidOfflineId(request))) { logger.info("Could not find a shop, can't authenticate request"); throw new Response(undefined, { status: 400, @@ -66,7 +77,7 @@ export class AuthCodeFlowStrategy< }); } - const offlineSession = await this.getOfflineSession(request, params); + const offlineSession = await this.getOfflineSession(request); const isEmbedded = url.searchParams.get('embedded') === '1'; if (!offlineSession) { @@ -87,74 +98,39 @@ export class AuthCodeFlowStrategy< logger.debug('Ensuring offline session is valid before embedding', { shop, }); - await this.testSession(offlineSession, params); + await this.testSession(offlineSession); logger.debug('Offline session is still valid, embedding app', {shop}); } catch (error) { - await this.handleInvalidOfflineSession( - error, - {api, logger, config}, - request, - shop, - ); + await this.handleInvalidOfflineSession(error, request, shop); } } } - private async getOfflineSession(request: Request, params: BasicParams) { - const {api, config} = params; - const url = new URL(request.url); - - const shop = url.searchParams.get('shop'); - - const offlineId = shop - ? api.session.getOfflineId(shop) - : await api.session.getCurrentId({isOnline: false, rawRequest: request}); - - if (!offlineId) { - return null; - } - - return config.sessionStorage.loadSession(offlineId); - } - - private async hasValidOfflineId(request: Request, params: BasicParams) { - const {api} = params; - const url = new URL(request.url); - - const shop = url.searchParams.get('shop'); - - const offlineId = shop - ? api.session.getOfflineId(shop) - : await api.session.getCurrentId({isOnline: false, rawRequest: request}); - - return Boolean(offlineId); - } - - private async handleBouncePageRoute(request: Request, params: BasicParams) { - const {config, logger} = params; + private async handleBouncePageRoute(request: Request) { + const {config, logger, api} = this; const url = new URL(request.url); if (url.pathname === config.auth.patchSessionTokenPath) { logger.debug('Rendering bounce page'); - throw renderAppBridge(params, request); + throw renderAppBridge({config, logger, api}, request); } } - private async handleExitIframeRoute(request: Request, params: BasicParams) { - const {config, logger} = params; + private async handleExitIframeRoute(request: Request) { + const {config, logger, api} = this; const url = new URL(request.url); if (url.pathname === config.auth.exitIframePath) { const destination = url.searchParams.get('exitIframe')!; logger.debug('Rendering exit iframe page', {destination}); - throw renderAppBridge(params, request, {url: destination}); + throw renderAppBridge({config, logger, api}, request, {url: destination}); } } - private async handleOAuthRoutes(request: Request, params: BasicParams) { - const {api, config} = params; + private async handleOAuthRoutes(request: Request) { + const {api, config} = this; const url = new URL(request.url); const isAuthRequest = url.pathname === config.auth.path; const isAuthCallbackRequest = url.pathname === config.auth.callbackPath; @@ -164,20 +140,18 @@ export class AuthCodeFlowStrategy< const shop = api.utils.sanitizeShop(url.searchParams.get('shop')!); if (!shop) throw new Response('Shop param is invalid', {status: 400}); - if (isAuthRequest) - throw await this.handleAuthBeginRequest(request, shop, params); + if (isAuthRequest) throw await this.handleAuthBeginRequest(request, shop); if (isAuthCallbackRequest) { - throw await this.handleAuthCallbackRequest(request, shop, params); + throw await this.handleAuthCallbackRequest(request, shop); } } private async handleAuthBeginRequest( request: Request, shop: string, - params: BasicParams, ): Promise { - const {api, config, logger} = params; + const {api, config, logger} = this; logger.info('Handling OAuth begin request'); @@ -198,9 +172,8 @@ export class AuthCodeFlowStrategy< private async handleAuthCallbackRequest( request: Request, shop: string, - params: BasicParams, ): Promise { - const {api, config, logger} = params; + const {api, config, logger} = this; logger.info('Handling OAuth callback request'); @@ -213,24 +186,59 @@ export class AuthCodeFlowStrategy< if (config.useOnlineTokens && !session.isOnline) { logger.info('Requesting online access token for offline session'); - await beginAuth(params, request, true, shop); + await beginAuth({api, config, logger}, request, true, shop); } - await triggerAfterAuthHook(params, session, request); + await triggerAfterAuthHook( + {api, config, logger}, + session, + request, + ); - throw await redirectToShopifyOrAppRoot(request, params, responseHeaders); + throw await redirectToShopifyOrAppRoot( + request, + {api, config, logger}, + responseHeaders, + ); } catch (error) { if (error instanceof Response) throw error; - throw await this.oauthCallbackError(params, error, request, shop); + throw await this.oauthCallbackError(error, request, shop); + } + } + + private async getOfflineSession(request: Request) { + const {api, config} = this; + const url = new URL(request.url); + + const shop = url.searchParams.get('shop'); + + const offlineId = shop + ? api.session.getOfflineId(shop) + : await api.session.getCurrentId({isOnline: false, rawRequest: request}); + + if (!offlineId) { + return null; } + + return config.sessionStorage.loadSession(offlineId); + } + + private async hasValidOfflineId(request: Request) { + const {api} = this; + const url = new URL(request.url); + + const shop = url.searchParams.get('shop'); + + const offlineId = shop + ? api.session.getOfflineId(shop) + : await api.session.getCurrentId({isOnline: false, rawRequest: request}); + + return Boolean(offlineId); } - private async testSession( - session: Session, - params: BasicParams, - ): Promise { - const {api} = params; + private async testSession(session: Session): Promise { + const {api} = this; const client = new api.clients.Graphql({ session, @@ -248,16 +256,15 @@ export class AuthCodeFlowStrategy< } private async oauthCallbackError( - params: BasicParams, error: Error, request: Request, shop: string, ) { - const {logger} = params; + const {logger} = this; logger.error('Error during OAuth callback', {error: error.message}); if (error instanceof CookieNotFound) { - return this.handleAuthBeginRequest(request, shop, params); + return this.handleAuthBeginRequest(request, shop); } if ( @@ -278,11 +285,10 @@ export class AuthCodeFlowStrategy< private async handleInvalidOfflineSession( error: Error, - params: BasicParams, request: Request, shop: string, ) { - const {api, logger, config} = params; + const {api, logger, config} = this; if (error instanceof HttpResponseError) { if (error.response.code === 401) { logger.info('Shop session is no longer valid, redirecting to OAuth', {