Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor oauth strategy #491

Merged
merged 12 commits into from
Nov 29, 2023
Merged

Refactor oauth strategy #491

merged 12 commits into from
Nov 29, 2023

Conversation

rezaansyed
Copy link
Contributor

@rezaansyed rezaansyed commented Nov 13, 2023

WHY are these changes introduced?

Part of https://github.com/Shopify/develop-app-access/issues/134

This change enables us to add token exchange in a following PR. This does not change any behaviour to the current OAuth authorization code flow. It is simply a refactor of the AuthStrategy class.

WHAT is this pull request doing?

  • Extracts auth code flow logic into AuthCodeFlowStrategy class, which is injected into the AuthStrategy class.
    • This enables us to add the eventual Token Exchange strategy class with minimum complexity and footprint.

Type of change

  • Patch: Bug (non-breaking change which fixes an issue)
  • Minor: New feature (non-breaking change which adds functionality) This is a refactor. No functionality added or removed.
  • Major: Breaking change (fix or feature that would cause existing functionality to not work as expected)

Checklist

  • I have used yarn changeset to create a draft changelog entry (do NOT update the CHANGELOG.md files manually)
  • I have added/updated tests for this change
  • I have documented new APIs/updated the documentation for modified APIs (for public APIs)

@rezaansyed rezaansyed force-pushed the refactor-oauth-strategy branch 3 times, most recently from fea7dc6 to 1231790 Compare November 14, 2023 20:34
@rezaansyed rezaansyed force-pushed the refactor-oauth-strategy branch from d1f6e4b to c6ac503 Compare November 15, 2023 20:58
@rezaansyed rezaansyed marked this pull request as ready for review November 15, 2023 21:23
@rezaansyed rezaansyed requested a review from a team as a code owner November 15, 2023 21:23
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can remove this test, if we're okay with keeping auth-callback-path.test.ts and auth-path.test.ts

@rezaansyed rezaansyed marked this pull request as draft November 16, 2023 16:30
@rezaansyed rezaansyed marked this pull request as ready for review November 16, 2023 21:11
Copy link
Contributor

@byrichardpowell byrichardpowell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As messaged on Slack i'd like to step through through these changes together on tuple to understand them a bit more. Without that high speed back n forth it's quite a lot to understand and quite hard to do the review justice

Adding a mini review now, but let's chat before actioning anything here

'@shopify/shopify-app-remix': minor
---

Refactor AuthStrategy to extract OAuth authorization code flow behaviour into a separate class.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since partners won't care about this, I'm not sure if this needs a release note? Thoughts @paulomarg


return this.validateAuthenticatedSession(request, sessionToken);
} else {
if (!sessionTokenHeader) {
await this.validateUrlParams(request);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both the session token and the URL params expire and can only be generated using the API secret. Given that, does token exchange need to validateURL Params?

packages/shopify-app-remix/src/server/shopify-app.ts Outdated Show resolved Hide resolved
Comment on lines 141 to 50
const {config, logger, api} = this;
const url = new URL(request.url);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this is path specific logic is really scattered and difficult to see all the routes we handle, and this url parsing is repeated too many times throughout this common handleRoutes logic and the strategy specific handleRoutes logic.

Just throwing some ideas:

  1. Would it benefit from using a switch statement so that we can see from a glance all the routes we handle
private async handleRoutes(request: Request) {
  const {config, logger, api} = this;
  const url = new URL(request.url); 

  switch(url.pathname) {
    case config.auth.patchSessionTokenPath:
       logger.debug('Rendering bounce page');
      throw renderAppBridge({config, logger, api}, request); 
    case config.auth.exitIframePath:
      const destination = url.searchParams.get('exitIframe')!;
      logger.debug('Rendering exit iframe page', {destination});
      throw renderAppBridge({config, logger, api}, request, {url: destination});
   .... 
     etc
  } 
}
  1. If we do what Richard suggested here

the handleRoutes logic can be in the common class so that:

// ******* Common Class
private async handleRoutes(request: Request) {
  const {config, logger, api} = this;
  const url = new URL(request.url); 

  switch(url.pathname) {
    case config.auth.patchSessionTokenPath:
       logger.debug('Rendering bounce page');
      throw renderAppBridge({config, logger, api}, request); 
    case config.auth.exitIframePath:
      const destination = url.searchParams.get('exitIframe')!;
      logger.debug('Rendering exit iframe page', {destination});
      throw renderAppBridge({config, logger, api}, request, {url: destination});
   .... 
     etc
  } 
}

// ********** Auth strategy specific class:
private async handleRoutes(request: Request) {
  const {config, logger, api} = this;
  const url = new URL(request.url); 

  switch(url.pathname) {
    case config.auth.path:
       throw await this.handleAuthBeginRequest(request, shop)
    case config.auth.callbackPath:
       throw await this.handleAuthCallbackRequest(request, shop)
    default  :
       // Call common class's method `handleRoutes` to handle "patchSessionTokenPath" & "exitIframePath" by default
   .... 
     etc
  } 
}

const isEmbedded = url.searchParams.get('embedded') === '1';

if (!offlineSession) {
logger.info("Shop hasn't installed app yet, redirecting to OAuth", {
Copy link

@owlyowl owlyowl Nov 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This log displays for people using online tokens it's a bit of a misnomer

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should happen in both cases - using online tokens causes OAuth to create both offline and online, and we use the presence of the offline token as indication that the app was installed (because it doesn't expire).

@rezaansyed rezaansyed force-pushed the refactor-oauth-strategy branch from fa92983 to 62e6a1a Compare November 28, 2023 15:06
Copy link
Contributor

@paulomarg paulomarg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, I had a bunch of nits but none were blocking.

const isEmbedded = url.searchParams.get('embedded') === '1';

if (!offlineSession) {
logger.info("Shop hasn't installed app yet, redirecting to OAuth", {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should happen in both cases - using online tokens causes OAuth to create both offline and online, and we use the presence of the offline token as indication that the app was installed (because it doesn't expire).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like having these in the same file, but the file name is technically wrong now 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that renaming will bring about changes to a bunch of new files. For that reason, let's just do that as part of another PR? Given how big this one is.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah no problem

@rezaansyed rezaansyed force-pushed the refactor-oauth-strategy branch 9 times, most recently from d90c52d to 36009f5 Compare November 28, 2023 21:29
@rezaansyed rezaansyed force-pushed the refactor-oauth-strategy branch from 36009f5 to 887eb77 Compare November 28, 2023 21:30
@rezaansyed rezaansyed force-pushed the refactor-oauth-strategy branch from 887eb77 to 2aa5ad4 Compare November 29, 2023 16:22
@rezaansyed rezaansyed merged commit 4260a0a into main Nov 29, 2023
10 checks passed
@rezaansyed rezaansyed deleted the refactor-oauth-strategy branch November 29, 2023 16:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants