slackapi / bolt-js

A framework to build Slack apps using JavaScript
https://tools.slack.dev/bolt-js/
MIT License
2.74k stars 393 forks source link

How should errors thrown in the AuthorizeFn be handled #1658

Closed mjdavidson closed 1 year ago

mjdavidson commented 1 year ago

Description

When using the authorize function as seen here, what should be done when an error is thrown? throw new Error('No matching authorizations');

At the moment my code looks the same as the example in the docs, but when an authorization isn't found the app blows up and says failed with the error "dispatch_failed". I would like to prompt the user to re-authorize.

I haven't been able to find an example in the documentation that handles the case when an authorization doesn't exist

What type of issue is this? (place an x in one of the [ ])

Requirements (place an x in each of the [ ])


Bug Report

Filling out the following details about bugs will help us solve your issue sooner.

Reproducible in:

package version: 3.8.1

node version: 16.14.0

OS version(s): macOS Ventura

Steps to reproduce:

  1. Create example app from https://slack.dev/bolt-js/concepts#authorization
  2. Try to send a command when authorization doesn't exist

Expected result:

Ask to re-authenticate

Actual result:

Receive failed with the error "dispatch_failed"

seratch commented 1 year ago

Hi @mjdavidson, thanks for asking the question!

In this case, you can customize your app's behavior by adjusting a bit more details of the underlying HTTPReceiver: https://slack.dev/bolt-js/concepts#error-handling More specifically, handling the exception in processEventErrorHandler and returning some message like you usually do with ack() method and/or performing a web API call such as chat.postMessage/chat.postEphemeral or whatever. If you're using SocketModeReceiver, the receiver supports the same set of error handlers too.

Slash command patterns would be quite simple. Just sending things to tell in the response body works. However, for other patterns, how to handle the unexpected error can be a bit more complex.

For instance, in the case of Events API request patterns, your app cannot directly respond to it (imagine events like channel_left). In this case, your app needs to go with more indirect way such as opening a DM with the user and then telling what happened. Perhaps, you can say "To use this app, please go through the installation process from here (link)" or something like that.

As for user interaction patterns such as button clicks on a channel message, a good way to go would be using response_url, which is the underlying mechanism of respond() utility. You can use the URL to send notifications to the place where the interaction happened even when the authorize fails. You can use the processEventErrorHandler for this too, but an easier way is to use app.error handler as the handler provides respond utility. If you're interested in more details, what we do in the official Java SDK may be helpful for learning what to do: https://github.com/slackapi/java-slack-sdk/blob/v1.27.1/bolt/src/main/java/com/slack/api/bolt/middleware/builtin/MultiTeamsAuthorization.java#L205-L233

I hope this was helpful to you!

mjdavidson commented 1 year ago

Hey @seratch, thanks for your quick response! We are using the ExpressReceiver, so the processEventErrorHandler doesn't exist. All I really want is a way to send a message if there is no authorization.

seratch commented 1 year ago

@mjdavidson If you're fine to upgrade bolt-js version, the handler exists for ExpressReceiver too! https://github.com/slackapi/bolt-js/blob/%40slack/bolt%403.12.2/src/receivers/ExpressReceiver.ts#L181

mjdavidson commented 1 year ago

OK great! I have upgraded to the latest bolt-js version, but still am a little unsure as to how I should be sending a message back to the user that they need to re-authenticate? The java sdk seems to have access to more properties inside the authorize function?

seratch commented 1 year ago

@mjdavidson If you go with response_url (respond() method in app.error), the URL is already associated with the channel where the user is now. So, just sending an ephemeral message using the URL works. This is what the Java SDK does. Inside processEventErrorHandler, you cannot use respond() method, but you can easily implement the same. It actually performs an HTTP POST request this way: https://github.com/slackapi/bolt-js/blob/@slack/bolt@3.12.2/src/App.ts#L1553-L1556

If you want to start a DM with the user, your code needs to extract the user ID from the body data structure. This may be a bit challenging because the payload data structure can vary.

mjdavidson commented 1 year ago

OK I'll give app.error a go and let you know how I get on. Cheers!

seratch commented 1 year ago

Please note that app.error handler is not capable of eliminating the "dispatch_failed" error on the end-user side. If you're fine to send a message while still having the "dispatch_failed" error, app.error works. Otherwise, you still need to do something with processEventErrorHandler.

Also, if your app needs to resolve this issue only for slash command invocations, just sending HTTP response body with your message in processEventErrorHandler better works for you.

Hope you'll figure out the best way to go!

mjdavidson commented 1 year ago

ahhhhh @seratch you're a legend, finally feel like i've made some progress on this issue!! thanks so much <3 <3 <3

image
mjdavidson commented 1 year ago

@seratch one last thing, I've handled errors in dispatchErrorHandler, processEventErrorHandler, and unhandledRequestHandler using the code from the example you linked above, I've also implemented app.error to send a specific error for the AuthorizationError case and also a generic message for all other errors. I feel like I've covered all the bases and I get the correct error back in Slack.

I still get failed with the error "operation_timeout" though, where have I gone wrong?

image
mjdavidson commented 1 year ago

@seratch looks like it works if you use unhandledRequestTimeoutMillis: 2000, not really sure why that happens

seratch commented 1 year ago

@mjdavidson Your app must return 200 OK HTTP response within 3 seconds. The ack() method does this for you under the hood. If you don't do so, the timeout error can occur. The reason why the unhandledRequestTimeoutMillis: 2000 works for you is perhaps your unhandledRequestHandler sends 200 OK anyways in 2.x seconds in the case.

It also works! but I would suggest:

Again, as I mentioned above, with bolt-js, app.error is not capable of acknowledging requests. If you use app.error for using response_url, acknowledgement needs to be done in processEventErrorHandler error handler.

mjdavidson commented 1 year ago

Thanks again mate!