Skip to content

Commit

Permalink
Pass in basic params into AuthCodeFlowStrategy constructor
Browse files Browse the repository at this point in the history
  • Loading branch information
rezaansyed committed Nov 14, 2023
1 parent 40ea175 commit 1231790
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
InvalidHmacError,
InvalidOAuthError,
Session,
Shopify,
ShopifyRestResources,
} from '@shopify/shopify-api';

Expand All @@ -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<SessionContext> {
const {config, logger} = params;
const {config, logger, api} = this;
if (
!sessionContext?.session ||
!sessionContext?.session.isActive(config.scopes)
Expand All @@ -43,30 +54,30 @@ 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');

// 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,
statusText: 'Bad Request',
});
}

const offlineSession = await this.getOfflineSession(request, params);
const offlineSession = await this.getOfflineSession(request);
const isEmbedded = url.searchParams.get('embedded') === '1';

if (!offlineSession) {
Expand All @@ -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;
Expand All @@ -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<never> {
const {api, config, logger} = params;
const {api, config, logger} = this;

logger.info('Handling OAuth begin request');

Expand All @@ -198,9 +172,8 @@ export class AuthCodeFlowStrategy<
private async handleAuthCallbackRequest(
request: Request,
shop: string,
params: BasicParams,
): Promise<never> {
const {api, config, logger} = params;
const {api, config, logger} = this;

logger.info('Handling OAuth callback request');

Expand All @@ -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<Resources>(params, session, request);
await triggerAfterAuthHook<Resources>(
{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<void> {
const {api} = params;
private async testSession(session: Session): Promise<void> {
const {api} = this;

const client = new api.clients.Graphql({
session,
Expand All @@ -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 (
Expand All @@ -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', {
Expand Down

0 comments on commit 1231790

Please sign in to comment.