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 21acd2cdde..d5f49b53d1 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/authenticate.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/authenticate.ts @@ -1,7 +1,7 @@ -import {Session, Shopify, ShopifyRestResources} from '@shopify/shopify-api'; +import {ShopifyRestResources} from '@shopify/shopify-api'; import type {BasicParams} from '../../types'; -import type {AppConfig, AppConfigArg} from '../../config-types'; +import type {AppConfigArg} from '../../config-types'; import { getSessionTokenHeader, ensureCORSHeadersFactory, @@ -10,7 +10,6 @@ import { respondToOptionsRequest, } from '../helpers'; -import type {BillingContext} from './billing/types'; import { cancelBillingFactory, requestBillingFactory, @@ -18,6 +17,7 @@ import { } from './billing'; import type { AdminContext, + AuthenticateAdmin, EmbeddedAdminContext, NonEmbeddedAdminContext, SessionContext, @@ -33,73 +33,16 @@ interface AuthStrategyParams extends BasicParams { strategy: AuthorizationStrategy; } -export class AuthStrategy< - Config extends AppConfigArg, +export function authStrategyFactory< + ConfigArg extends AppConfigArg, Resources extends ShopifyRestResources = ShopifyRestResources, -> { - protected strategy: AuthorizationStrategy; - protected api: Shopify; - protected config: AppConfig; - protected logger: Shopify['logger']; - - public constructor({strategy, api, config, logger}: AuthStrategyParams) { - this.api = api; - this.config = config; - this.logger = logger; - this.strategy = strategy; - } - - public async authenticateAdmin( - request: Request, - ): Promise> { - const {config, logger, api} = this; - const params = {api, logger, config}; - - try { - respondToBotRequest(params, request); - respondToOptionsRequest(params, request); - await this.respondToBouncePageRequest(params, request); - await this.respondToExitIframeRequest(params, request); - await this.strategy.respondToOAuthRequests(request); - - logger.info('Authenticating admin request'); - - const headerSessionToken = getSessionTokenHeader(request); - const searchParamSessionToken = getSessionTokenFromUrlParam(request); - const sessionToken = (headerSessionToken || searchParamSessionToken)!; - - logger.debug('Attempting to authenticate session token', { - sessionToken: { - header: headerSessionToken, - search: searchParamSessionToken, - }, - }); - - const sessionContext = await this.strategy.authenticate( - request, - sessionToken, - ); - - logger.debug('Request is valid, loaded session from session token', { - shop: sessionContext.session.shop, - isOnline: sessionContext.session.isOnline, - }); - - return this.createContext(request, sessionContext); - } catch (errorOrResponse) { - if (errorOrResponse instanceof Response) { - ensureCORSHeadersFactory(params, request)(errorOrResponse); - } - - throw errorOrResponse; - } - } +>({ + strategy, + ...params +}: AuthStrategyParams): AuthenticateAdmin { + const {api, logger, config} = params; - private async respondToBouncePageRequest( - params: BasicParams, - request: Request, - ) { - const {config, logger, api} = params; + async function respondToBouncePageRequest(request: Request) { const url = new URL(request.url); if (url.pathname === config.auth.patchSessionTokenPath) { @@ -108,11 +51,7 @@ export class AuthStrategy< } } - private async respondToExitIframeRequest( - params: BasicParams, - request: Request, - ) { - const {config, logger, api} = params; + async function respondToExitIframeRequest(request: Request) { const url = new URL(request.url); if (url.pathname === config.auth.exitIframePath) { @@ -123,21 +62,25 @@ export class AuthStrategy< } } - private createContext( + function createContext( request: Request, sessionContext: SessionContext, - ): AdminContext { - const {api, logger, config} = this; + ): AdminContext { + const {session} = sessionContext; const context: - | EmbeddedAdminContext - | NonEmbeddedAdminContext = { + | EmbeddedAdminContext + | NonEmbeddedAdminContext = { admin: createAdminApiContext(request, sessionContext.session, { api, logger, config, }), - billing: this.createBillingContext(request, sessionContext.session), + billing: { + require: requireBillingFactory({api, logger, config}, request, session), + request: requestBillingFactory({api, logger, config}, request, session), + cancel: cancelBillingFactory({api, logger, config}, request, session), + }, session: sessionContext.session, cors: ensureCORSHeadersFactory({api, logger, config}, request), }; @@ -147,22 +90,47 @@ export class AuthStrategy< ...context, sessionToken: sessionContext!.token!, redirect: redirectFactory({api, config, logger}, request), - } as AdminContext; + } as AdminContext; } else { - return context as AdminContext; + return context as AdminContext; } } - private createBillingContext( - request: Request, - session: Session, - ): BillingContext { - const {api, logger, config} = this; - - return { - require: requireBillingFactory({api, logger, config}, request, session), - request: requestBillingFactory({api, logger, config}, request, session), - cancel: cancelBillingFactory({api, logger, config}, request, session), - }; - } + return async function authenticateAdmin(request: Request) { + try { + respondToBotRequest(params, request); + respondToOptionsRequest(params, request); + await respondToBouncePageRequest(request); + await respondToExitIframeRequest(request); + await strategy.respondToOAuthRequests(request); + + logger.info('Authenticating admin request'); + + const headerSessionToken = getSessionTokenHeader(request); + const searchParamSessionToken = getSessionTokenFromUrlParam(request); + const sessionToken = (headerSessionToken || searchParamSessionToken)!; + + logger.debug('Attempting to authenticate session token', { + sessionToken: { + header: headerSessionToken, + search: searchParamSessionToken, + }, + }); + + const sessionContext = await strategy.authenticate(request, sessionToken); + + logger.debug('Request is valid, loaded session from session token', { + shop: sessionContext.session.shop, + isOnline: sessionContext.session.isOnline, + }); + + return createContext(request, sessionContext); + } catch (errorOrResponse) { + if (errorOrResponse instanceof Response) { + ensureCORSHeadersFactory(params, request)(errorOrResponse); + } + + throw errorOrResponse; + } + }; } diff --git a/packages/shopify-app-remix/src/server/shopify-app.ts b/packages/shopify-app-remix/src/server/shopify-app.ts index 2102d14104..a922f440ef 100644 --- a/packages/shopify-app-remix/src/server/shopify-app.ts +++ b/packages/shopify-app-remix/src/server/shopify-app.ts @@ -21,7 +21,7 @@ import { } from './types'; import {SHOPIFY_REMIX_LIBRARY_VERSION} from './version'; import {registerWebhooksFactory} from './authenticate/webhooks'; -import {AuthStrategy} from './authenticate/admin/authenticate'; +import {authStrategyFactory} from './authenticate/admin/authenticate'; import {authenticateWebhookFactory} from './authenticate/webhooks/authenticate'; import {overrideLogger} from './override-logger'; import {addDocumentResponseHeadersFactory} from './authenticate/helpers'; @@ -66,7 +66,7 @@ export function shopifyApp< const params: BasicParams = {api, config, logger}; const oauth = new AuthCodeFlowStrategy(params); - const authStrategy = new AuthStrategy({ + const authStrategy = authStrategyFactory({ ...params, strategy: oauth, }); @@ -79,7 +79,7 @@ export function shopifyApp< addDocumentResponseHeaders: addDocumentResponseHeadersFactory(params), registerWebhooks: registerWebhooksFactory(params), authenticate: { - admin: authStrategy.authenticateAdmin.bind(authStrategy), + admin: authStrategy, public: authenticatePublicFactory(params), webhook: authenticateWebhookFactory< Config['future'],