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

Extract post authenticate tasks from CallbackController #1819

Merged
merged 22 commits into from
Apr 2, 2024
Merged

Conversation

zzooeeyy
Copy link
Contributor

@zzooeeyy zzooeeyy commented Mar 26, 2024

What this PR does

  • https://github.com/Shopify/develop-app-access/issues/235

  • Extract common logic to run post authenticate task from CallbackController so that it can be used after the token exchange flow.

  • Added PostAuthenticateTasks to run the authenticated task

  • This is configurable as config.custom_post_authenticate_tasks, if that's not set, the default config.post_authenticate_tasks will return PostAuthenticateTasks class

  • Updated docs on post authenticate tasks

  • Added deprecation warnings

Tophatting:

Deprecation warning in server start from configuration

deprecation

Deprecation reminder in tests logs

test

When config.custom_post_authenticate_tasks is set, it will be used to handle post authenticate tasks

post-authenticate-jobs

  • I also tested not setting the custom_post_authenticate_tasks to ensure the methods in CallbackController to perform post authenticate tasks are done.

Checklist

Before submitting the PR, please consider if any of the following are needed:

  • Update CHANGELOG.md if the changes would impact users
  • Update README.md, if appropriate.
  • Update any relevant pages in /docs, if necessary
  • For security fixes, the Disclosure Policy must be followed.

@zzooeeyy zzooeeyy force-pushed the post-auth-tasks branch 11 times, most recently from 27a1a91 to 02d14ba Compare March 26, 2024 22:14
Comment on lines +7 to +43
def perform(session)
ShopifyApp::Logger.debug("Performing post authenticate tasks")
# Ensure we use the shop session to install webhooks
session_for_shop = session.online? ? shop_session(session) : session

install_webhooks(session_for_shop)

perform_after_authenticate_job(session)
end

private

def shop_session(session)
ShopifyApp::SessionRepository.retrieve_shop_session_by_shopify_domain(session.shop)
end

def install_webhooks(session)
ShopifyApp::Logger.debug("PostAuthenticateTasks: Installing webhooks")
return unless ShopifyApp.configuration.has_webhooks?

WebhooksManager.queue(session.shop, session.access_token)
end

def perform_after_authenticate_job(session)
ShopifyApp::Logger.debug("PostAuthenticateTasks: Performing after_authenticate_job")
config = ShopifyApp.configuration.after_authenticate_job

return unless config && config[:job].present?

job = config[:job]
job = job.constantize if job.is_a?(String)

if config[:inline] == true
job.perform_now(shop_domain: session.shop)
else
job.perform_later(shop_domain: session.shop)
end
Copy link
Contributor Author

Choose a reason for hiding this comment

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

All of this is copied from CallbackController

def perform_post_authenticate_jobs(session)
# Ensure we use the shop session to install webhooks
session_for_shop = session.online? ? shop_session : session
install_webhooks(session_for_shop)
perform_after_authenticate_job(session)
end
def install_webhooks(session)
return unless ShopifyApp.configuration.has_webhooks?
WebhooksManager.queue(session.shop, session.access_token)
end
def perform_after_authenticate_job(session)
config = ShopifyApp.configuration.after_authenticate_job
return unless config && config[:job].present?
job = config[:job]
job = job.constantize if job.is_a?(String)
if config[:inline] == true
job.perform_now(shop_domain: session.shop)
else
job.perform_later(shop_domain: session.shop)
end

@zzooeeyy zzooeeyy marked this pull request as ready for review March 26, 2024 23:58
@zzooeeyy zzooeeyy requested a review from a team as a code owner March 26, 2024 23:58
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.

Had a few thoughts but most of them are just me trying to understand things better so take them with a grain of salt :)

Comment on lines 26 to 36
# TODO: Remove before releasing v23.0.0
# "perform_after_authenticate_job" and related methods (install_webhooks, perform_after_authenticate_job)
# will be deprecated in the next major release - 23.0.0
# Set `custom_post_authenticate_tasks`from configuration instead to handle special cases.
if ShopifyApp.configuration.custom_post_authenticate_tasks.present?
ShopifyApp.configuration.post_authenticate_tasks.perform(api_session)
else
perform_post_authenticate_jobs(api_session)
end
########### When deprecating, replace the above block with:
# ShopifyApp.configuration.post_authenticate_tasks.perform(api_session)
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we simplify this by adding a check for the version itself instead of adding the comments? We should still have a comment with the word deprecated in it because that makes it easier to search / replace when we're releasing a new major version.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Alt Text

@@ -29,6 +29,9 @@ class Configuration
attr_writer :login_callback_url
attr_accessor :embedded_redirect_url

# customize post authenticate tasks
attr_accessor :custom_post_authenticate_tasks
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: would it make more sense to have a post_authenticate_tasks setting that defaults to the new class rather than a custom setting?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yess that does make sense, but in this pre-deprecation case we don't want to use the post_authenticate_tasks with a default if it's not manually set since we still want to call the old methods. I think it does make sense to have a single config with a default once we roll out the deprecation.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm yeah, fair. It wouldn't be ideal to change the setting name in the future though - so I think we should stick with what we add in this PR, whichever way we prefer to do it.


# TODO: Remove this before releasing v23.0.0
message = <<~EOS
================================================
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 this as a setup for logging general duplications - we might want to do that too when we go ahead with token exchange, so there might be an opportunity to make this a little bit more generic (like given an array of messages, log if it isn't empty).

Also, can we add some conditions to logging this? Is there any way we can detect that there is a controller that responds to the old methods we want to hide? We can probably build it back from the route path. It's more work but I think it would be worth it to not log every time.

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 agree we should make it more generic, but I think that could be done when we know we'll have another need for it? It's hard to know what specs to build for until we know how the new messages might look? e.g. is it generic messages or is it specific to deprecations...

and also, I disagree on conditionally logging this by checking for controllers in route.. I don't think that's worth the effort since it'll be deprecated in next release anyways, plus this will only be logged when you first start the server, so it's not tooo noisy. 🤔

Copy link
Contributor

Choose a reason for hiding this comment

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

Fair. I think this comment is big enough that most folks would see it.

When I suggested we log this conditionally, I meant that ideally we wouldn't log it if there is no usage of the deprecated setup, i.e. if the app is configured in the "new" way, we just skip this to avoid confusion.

@@ -174,6 +183,7 @@ class CallbackControllerTest < ActionController::TestCase
end

test "#callback doesn't run the WebhooksManager if no webhooks are configured" do
log_deprecation
Copy link
Contributor

Choose a reason for hiding this comment

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

Personal opinion, but I'm not sure I'd explicitly log these - just adding a comment saying they're deprecated (or better yet, adding actual deprecation logs) rather than puts-ing might be enough. The logger call would actually error out so it would create actual test failures when we get there, which is nice.

@@ -333,6 +352,32 @@ class CallbackControllerTest < ActionController::TestCase
assert_response 302
end

test "#callback calls post_authenticate_tasks if custom_post_authenticate_tasks is set" do
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we have a very simple test here to ensure that the default behaviour gets called (even if we have to stub it)?

ShopifyApp.configure do |config|
config.webhooks = []
end
ShopifyApp::WebhooksManager.add_registrations
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: why do we have to call add_registrations here but not in the success tests? Makes me feel like there's a wrong check somewhere in the code :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ooops that's my oversight, I copied straight from CallbackControllTest LOL I'll also remove it from that test case

https://github.com/Shopify/shopify_app/blob/main/test/controllers/callback_controller_test.rb#L180

@@ -2,6 +2,18 @@

require "test_helper"

module Shopify
class CustomPostAuthenticateTasks
Copy link
Contributor

Choose a reason for hiding this comment

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

This is just a question for my own benefit: will any class that defines a perform method do here? Should we check if it extends a job class instead to standardize things a bit more?

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 didn't put too much consideration there, because I didn't think the standardization is too necessary? This way it's more flexible, plus the configuration will raise a ConfigurationError if "perform" isn't a method.. What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

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

I also don't think it matters too much, but I remember the documentation said it could "run this later", which I took to mean it would deal with a perform_later type of thing. But just for the sake of standardization, I don't think it's necessary.

@zzooeeyy zzooeeyy requested a review from paulomarg March 27, 2024 19:56
@@ -2,6 +2,18 @@

require "test_helper"

module Shopify
class CustomPostAuthenticateTasks
Copy link
Contributor

Choose a reason for hiding this comment

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

I also don't think it matters too much, but I remember the documentation said it could "run this later", which I took to mean it would deal with a perform_later type of thing. But just for the sake of standardization, I don't think it's necessary.

@@ -23,7 +23,16 @@ def callback

return respond_with_user_token_flow if start_user_token_flow?(api_session)

perform_post_authenticate_jobs(api_session)
if ShopifyApp::VERSION < "23.0"
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice that we can just compare it this way, makes this very easy to understand :)

docs/Upgrading.md Outdated Show resolved Hide resolved
@@ -29,6 +29,9 @@ class Configuration
attr_writer :login_callback_url
attr_accessor :embedded_redirect_url

# customize post authenticate tasks
attr_accessor :custom_post_authenticate_tasks
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm yeah, fair. It wouldn't be ideal to change the setting name in the future though - so I think we should stick with what we add in this PR, whichever way we prefer to do it.


# TODO: Remove this before releasing v23.0.0
message = <<~EOS
================================================
Copy link
Contributor

Choose a reason for hiding this comment

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

Fair. I think this comment is big enough that most folks would see it.

When I suggested we log this conditionally, I meant that ideally we wouldn't log it if there is no usage of the deprecated setup, i.e. if the app is configured in the "new" way, we just skip this to avoid confusion.

@@ -47,6 +54,7 @@ class CallbackControllerTest < ActionController::TestCase
"AppleWebKit/537.36 (KHTML, like Gecko)"\
"Chrome/69.0.3497.100 Safari/537.36"
ShopifyApp::SessionRepository.stubs(:store_session)
ShopifyApp.configuration.custom_post_authenticate_tasks = nil
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be the global test config value, so that other tests start with this as an assumption?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not necessary I only did it to "reset" the configuration state, but I can move that to the teardown so it doesn't become and assumption for other tests!

zzooeeyy and others added 3 commits March 27, 2024 16:14
Co-authored-by: Paulo Margarido <64600052+paulomarg@users.noreply.github.com>
@zzooeeyy zzooeeyy requested a review from paulomarg March 28, 2024 14:38
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.

Just a general question, but if this is the way, LGTM

@@ -69,11 +63,39 @@ Rails.application.routes.draw do
end
```

### Run jobs after the OAuth flow
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: this breaks the ToC at the top of this file :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch!

Comment on lines +76 to +78
If your post authentication tasks is more complex and is different than just installing webhooks and enqueuing jobs,
you can customize the post authenticate tasks by creating a new class that has a `self.perform(session)` method,
and configuring `custom_post_authenticate_tasks` in the initializer.
Copy link
Contributor

Choose a reason for hiding this comment

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

I know this is a bit late in the game, but isn't this essentially the same as enqueueing a special job that does these tasks?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is very similar, but the after_authenticate_job only accepts a shop domain as the parameter, in which case it might not be enough for task customization?? If devs want to use the user session for whatever post auth task for their app, they wouldn't be able to since the task would only know the shop domain?? And since that's also setup in the configuration file, we'll need to somehow magically add this new special job to existing apps, and ensure there isn't conflict if they have already declared jobs?

@zzooeeyy zzooeeyy merged commit 41bb033 into main Apr 2, 2024
7 checks passed
@zzooeeyy zzooeeyy deleted the post-auth-tasks branch April 2, 2024 16:36
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.

None yet

2 participants