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

An unhandled error occurred while Bolt processed an event #1777

Closed anilskalyane closed 1 year ago

anilskalyane commented 1 year ago

We are facing the following issue with the Slack app when we are trying to add the app to another workspace (different from the one where it was created). Once the authorization is given, we are able to read all the credentials including team id, access token, etc. However, when we try to use the same credentials to access the workspace for any activity,

we are getting the following error:

2023-03-20T18:07:34: [ERROR] An unhandled error occurred while Bolt processed an event
2023-03-20T18:07:34: [DEBUG] Error details: TypeError: Cannot read property 'teamId' of undefined, storedResponse: undefined

I think getting this response from Slack bolt,

In the first line, I'm printing the teamId and same also passing it to the bolt lib(authorizer),

0|slack-app | 2023-03-20T18:10:58: TQXMVxxxx
0|slack-app | 2023-03-20T18:10:58: [ERROR] An unhandled error occurred while Bolt processed an event
0|slack-app | 2023-03-20T18:10:58: [DEBUG] Error details: TypeError: Cannot read property 'teamId' of undefined, storedResponse: undefined

Code:

const authorizeFn = async ({ teamId, enterpriseId }) => {
    console.log(teamId);
    // Fetch team info from database
    db.mongoose
        .connect(process.env.DB_CONNECTION_STRING, {
            useNewUrlParser: true,
            useUnifiedTopology: true
        })
        .then(() => {
            console.log("Connected to the database!");
        })
        .catch(err => {
            console.log("Cannot connect to the database!", err);
            process.exit();
        });
    Slackbot.find({ "access_details.team.id": teamId })
        .then(data => {
            if (!data)
                console.log("Not found slack user with team " + teamId);
            else {
                console.log(data[0].access_details);
                return {
                    // You could also set userToken instead
                    botToken: data[0].access_details.access_token,
                    botId: data[0].access_details.app_id,
                    botUserId: data[0].access_details.bot_user_id
                };
            }
        })
        .catch(err => {
            console.log("Error retrieving slack with team=" + teamId);
        });
}

// Initializes your app with your bot token and app token
const app = new App({
    appToken: process.env.SLACK_APP_TOKEN,
    signingSecret: process.env.SLACK_SIGNING_SECRET,
    logLevel: LogLevel.DEBUG,
    customRoutes: customRoutes.customRoutes,
    processBeforeResponse: true,
    stateSecret: 'my-state-secret',
    scopes: [process.env.SLACK_SCOPES],
    authorize: authorizeFn
});
filmaj commented 1 year ago

The following error:

[DEBUG] Error details: TypeError: Cannot read property 'teamId' of undefined, storedResponse: undefined

Comes from one of two locations:

  1. From the AWSLambdaReceiver: https://github.com/slackapi/bolt-js/blob/main/src/receivers/AwsLambdaReceiver.ts#L203
  2. From the HTTPModuleFunction: https://github.com/slackapi/bolt-js/blob/main/src/receivers/HTTPModuleFunctions.ts#L204

First question: are you using this app on AWS Lambda, and using it with the AWS Lambda receiver that bolt provides? I believe the answer is "no", as to configure bolt-js for use on AWS Lambda, you must manually instantiate the receiver class and pass it as a receiver property to your App constructor. However, what confuses me is you are using the processBeforeResponse property, which is aimed for use exclusively with the AWS Lambda Receiver. We should clarify this point and ensure you are not setting processBeforeResponse unnecessarily, as this could also further confuse bolt execution.

Second, I am not exactly sure what is happening in your authorize function, but some observations:

If my assumption that Slackbot relies on the mongoose DB connection, then to fix the potential for race conditions, you could change your authorize function to use await to make sure that the code executes sequentially. Something like the following would address that:

const authorizeFn = async ({ teamId, enterpriseId }) => {
    console.log(teamId);
    // Fetch team info from database
    try {
        await db.mongoose
            .connect(process.env.DB_CONNECTION_STRING, {
                useNewUrlParser: true,
                useUnifiedTopology: true
            });
        console.log("Connected to the database!");
    } catch (err) {
        console.log("Cannot connect to the database!", err);
        process.exit();
    }
    const data = await Slackbot.find({ "access_details.team.id": teamId });
    if (!data)
        console.log("Not found slack user with team " + teamId);
    else {
        console.log(data[0].access_details);
        return {
            // You could also set userToken instead
            botToken: data[0].access_details.access_token,
            botId: data[0].access_details.app_id,
            botUserId: data[0].access_details.bot_user_id
        };
    }
}
anilskalyane commented 1 year ago

Thanks for the quick response.

First question ---> Yes, We are not using the Lambda environment. Also removed the processBeforeResponse param but not luck

Second question: Small info on the Authorization function: Authorization is the process of deciding which Slack credentials (such as a bot token) should be available while processing a specific incoming request.

MongoDB connections and fetching the data part are working as expected but It's failing once auth data is passed to Bolt.

FYI Logs:

0|slack-app  | 2023-03-21T19:56:21: TQXMXXXXX
0|slack-app  | 2023-03-21T19:56:21: Connected to the database!
0|slack-app  | 2023-03-21T19:56:21: {
0|slack-app  | 2023-03-21T19:56:21:   ok: true,
0|slack-app  | 2023-03-21T19:56:21:   app_id: 'xxxxx',
0|slack-app  | 2023-03-21T19:56:21:   authed_user: { id: 'xxxxx' },
0|slack-app  | 2023-03-21T19:56:21:   scope: 'team:read,app_mentions:read,channels:history,chat:write,commands,im:history,im:read,im:write,users.profile:read,users:read,users:read.email',
0|slack-app  | 2023-03-21T19:56:21:   token_type: 'bot',
0|slack-app  | 2023-03-21T19:56:21:   access_token: 'xoxb-xxxxxx-xxxx-xxxxxxx',
0|slack-app  | 2023-03-21T19:56:21:   bot_user_id: 'xxxxxx',
0|slack-app  | 2023-03-21T19:56:21:   team: { id: 'xxxxxx', name: 'xxxxxxx' },
0|slack-app  | 2023-03-21T19:56:21:   enterprise: null,
0|slack-app  | 2023-03-21T19:56:21:   is_enterprise_install: false
0|slack-app  | 2023-03-21T19:56:21: }
0|slack-app  | 2023-03-21T19:56:21: [ERROR]   An unhandled error occurred while Bolt processed an event
0|slack-app  | 2023-03-21T19:56:21: [DEBUG]   Error details: TypeError: Cannot read property 'teamId' of undefined, storedResponse: undefined

please let me know if you require any further details

filmaj commented 1 year ago

It looks to me like the code now is returning installation data, so progress! But, there is still a Bolt error. Let's take a further look.

This error message:

[ERROR] An unhandled error occurred while Bolt processed an event

... comes, I believe, from this location in the source code. Given that your authorize function returns a result now (based on your latest logs), I believe this line of Bolt code successfully passes now. However, somewhere else in the enclosing processEvent method is throwing an exception related to access teamId on something that is undefined. So when the HTTP Receiver for your app calls processEvent here, the catch clause here is triggered, and that is where the final error logging in your last message is coming from.

The question now is, where in Bolt's processEvent method is Bolt attempting to access teamId on an undefined variable?

To make it easier to parse through and figure that out, could you set developerMode to true and socketMode to false in your App constructor and run again and post the logs? The reason I ask for this is to trigger this logging in Bolt so that we can see the details of the event from Slack triggering the exception. This will make it easier to trace through the logic and try to determine why this exception is happening for you.

Also, one more question: what version of bolt-js are you using?

anilskalyane commented 1 year ago

Here are the config and logs,

const app = new App({
    appToken: process.env.SLACK_APP_TOKEN,
    signingSecret: process.env.SLACK_SIGNING_SECRET,
    logLevel: LogLevel.DEBUG,
    customRoutes: customRoutes.customRoutes,
    scopes: [process.env.SLACK_SCOPES],
    authorize: authorizeFn,
    developerMode: true
});
0|slack-app  | 2023-03-21T20:52:14: [INFO]   An unhandled HTTP request (POST) made to /slack/events was ignored
0|slack-app  | 2023-03-21T20:52:15: [INFO]   An unhandled HTTP request (POST) made to /slack/events was ignored
0|slack-app  | 2023-03-21T20:52:31: [INFO]   An unhandled HTTP request (POST) made to /slack/events was ignored
0|slack-app  | 2023-03-21T20:53:15: [INFO]   An unhandled HTTP request (POST) made to /slack/events was ignored
0|slack-app  | 2023-03-21T20:53:45: [INFO]   An unhandled HTTP request (POST) made to /slack/events was ignored

Bolt Version - @slack/bolt@3.12.2

filmaj commented 1 year ago

@anilskalyane please also set socketMode: false on your app constructor.

anilskalyane commented 1 year ago

Added the socket mode also, @filmaj

const app = new App({
    appToken: process.env.SLACK_APP_TOKEN,
    signingSecret: process.env.SLACK_SIGNING_SECRET,
    logLevel: LogLevel.DEBUG,
    customRoutes: customRoutes.customRoutes,
    scopes: [process.env.SLACK_SCOPES],
    authorize: authorizeFn,
    developerMode: true,
    socketMode: false
});
0|slack-app  | 2023-03-21T21:48:31: [DEBUG]  bolt-app {"token":"4sND1EQy5IGQaJGjvIguLynn","team_id":"TQXMxxxxx","context_team_id":"TQXMxxxxx","context_enterprise_id":null,"api_app_id":"A04FLDxxxxx","event":{"client_msg_id":"xxxxx-1a99-45e8-a7ef-94ecce2033ae","type":"message","text":"hifives","user":"U01Vxxxxxxx","ts":"1679415510.997069","blocks":[{"type":"rich_text","block_id":"gz3OT","elements":[{"type":"rich_text_section","elements":[{"type":"text","text":"hifives"}]}]}],"team":"TQXMxxxxx","channel":"D04U2xxxxxx","event_ts":"1679415510.997069","channel_type":"im"},"type":"event_callback","event_id":"Ev04VC8Z2K2M","event_time":1679415510,"authorizations":[{"enterprise_id":null,"team_id":"TQXMxxxxx","user_id":"U04U2xxxxxx","is_bot":true,"is_enterprise_install":false}],"is_ext_shared_channel":false,"event_context":"4-eyJldCI6Im1lc3NhZ2UiLCJ0aWQiOiJUUVhNVlNIUTgiLCJhaWQiOiJBMDRGTEQyVTkwVCIsImNpZCI6IkQwNFUyVFBHWTlaIn0"}
0|slack-app  | 2023-03-21T21:48:31: TQXMxxxxx
0|slack-app  | 2023-03-21T21:48:31: Connected to the database!
0|slack-app  | 2023-03-21T21:48:32: {
0|slack-app  | 2023-03-21T21:48:32:   ok: true,
0|slack-app  | 2023-03-21T21:48:32:   app_id: 'A04xxxxx',
0|slack-app  | 2023-03-21T21:48:32:   authed_user: { id: 'U01V2xxxxx' },
0|slack-app  | 2023-03-21T21:48:32:   scope: 'team:read,app_mentions:read,channels:history,chat:write,commands,im:history,im:read,im:write,users.profile:read,users:read,users:read.email',
0|slack-app  | 2023-03-21T21:48:32:   token_type: 'bot',
0|slack-app  | 2023-03-21T21:48:32:   access_token: 'xoxb-xxxxxxx-xxxxxx-xxxxxxxx',
0|slack-app  | 2023-03-21T21:48:32:   bot_user_id: 'U04U2xxxxxx',
0|slack-app  | 2023-03-21T21:48:32:   team: { id: 'TQXMxxxxx', name: 'xxxxxx' },
0|slack-app  | 2023-03-21T21:48:32:   enterprise: null,
0|slack-app  | 2023-03-21T21:48:32:   is_enterprise_install: false
0|slack-app  | 2023-03-21T21:48:32: }
0|slack-app  | 2023-03-21T21:48:32: [ERROR]   An unhandled error occurred while Bolt processed an event
0|slack-app  | 2023-03-21T21:48:32: [DEBUG]   Error details: TypeError: Cannot read property 'teamId' of undefined, storedResponse: undefined
filmaj commented 1 year ago

Sorry for the delay, I have not forgotten about this, just caught up in some other work related stuff! As soon as I have some time to dig deeper in here, I will do so.

anilskalyane commented 1 year ago

@filmaj - Thanks for looking into this! I'll be happy to provide more details and try some other things if it helps

filmaj commented 1 year ago

So I tried to just trace through the code to see if I could glean where things are breaking.. but bolt is of such complexity that once it became time to trace through the middleware-processing code Bolt has, I gave up.

I think a better angle is to try to reproduce the issue. To that end, how can I reproduce this? It sounds like you are "adding this app to another workspace (different from where the app was created)." How did you add this app to the new workspace? Typically this is done using an OAuth flow. Is that the case here? How are you managing the OAuth dance? Looks like your app is adding 'custom routes' - is it through those custom routes that you are managing the installation route (OAuth entry point) as well as the OAuth authorization route?

If so, this is a out-of-the-box feature that Bolt provides (to manage these OAuth endpoints) that we call Installation Store - is there a reason you are not using that feature?

anilskalyane commented 1 year ago

@filmaj thanks for your time and patience.

Yes, I'm trying to install the app to a different workspace using OAuth flow. here is the complete code for your reference,

const { App, LogLevel } = require('@slack/bolt');
const HifivesApi = require('./src/controllers/HifivesApi.js');
const db = require("./src/models");
const Slackbot = db.slackbot;
const app_menu = require('./src/config/menu.json')

const databaseData = {};
const database = {
  set: async (key, data) => {
    databaseData[key] = data
  },
  get: async (key) => {
    return databaseData[key];
  },
};

const authorizeFn = async ({ teamId, enterpriseId }) => {
    console.log(teamId);
    // Fetch team info from database
    await db.mongoose
        .connect(process.env.DB_CONNECTION_STRING, {
            useNewUrlParser: true,
            useUnifiedTopology: true
        })
        .then(() => {
            console.log("Connected to the database!");
        })
        .catch(err => {
            console.log("Cannot connect to the database!", err);
            process.exit();
        });
    await Slackbot.find({ "access_details.team.id": teamId })
        .then(data => {
            if (!data)
                console.log("Not found slack user with team " + teamId);
            else {
                console.log(data[0].access_details);
                return {
                    // You could also set userToken instead
                    botToken: data[0].access_details.access_token,
                    botId: data[0].access_details.app_id,
                    botUserId: data[0].access_details.bot_user_id
                };
            }
        })
        .catch(err => {
            console.log("Error retrieving slack with team=" + teamId);
        });
}

// Initializes your app with your bot token and app token
const app = new App({
    socketMode: false,
    appToken: process.env.SLACK_APP_TOKEN,
    signingSecret: process.env.SLACK_SIGNING_SECRET,
    logLevel: LogLevel.DEBUG,
    stateSecret: 'my-state-secret',
    scopes: [process.env.SLACK_SCOPES],
    authorize: authorizeFn,
    developerMode: true,
    installationStore: {
        storeInstallation: async (installation) => {
          if (installation.isEnterpriseInstall && installation.enterprise !== undefined) {
            return await database.set(installation.enterprise.id, installation);
          }
          if (installation.team !== undefined) {
            return await database.set(installation.team.id, installation);
          }
          throw new Error('Failed saving installation data to installationStore');
        },
        fetchInstallation: async (installQuery) => {
          if (installQuery.isEnterpriseInstall && installQuery.enterpriseId !== undefined) {
            return await database.get(installQuery.enterpriseId);
          }
          if (installQuery.teamId !== undefined) {
            return await database.get(installQuery.teamId);
          }
          throw new Error('Failed fetching installation');
        },
        deleteInstallation: async (installQuery) => {
          if (installQuery.isEnterpriseInstall && installQuery.enterpriseId !== undefined) {
            return await database.delete(installQuery.enterpriseId);
          }
          if (installQuery.teamId !== undefined) {
            return await database.delete(installQuery.teamId);
          }
          throw new Error('Failed to delete installation');
        },
      },
      installerOptions: {
        directInstall: true,
      }
});

app.error(async (error) => {
    // Check the details of the error to handle cases where you should retry sending a message or stop the app
    console.error(error);
  });

app.message('hifives', async ({ message, client, logger, ack }) => {
    console.log("message data", message);
    try {
        ack();
        await client.chat.postEphemeral({
            channel: message.channel,
            user: message.user,
            blocks: [{
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": `Welcome <@${message.user}>! \n\nWhat would like to do today?`
                }
            },
            {
                "type": "divider"
            },
                app_menu
            ]
        });
    }
    catch (error) {
        logger.error(error);
    }
});

async function userBasicDetails(userId) {
    try {
        // Call the users.info method using the WebClient
        const result = await app.client.users.info({
            user: userId
        });
        //console.log(result.user.profile);
        return result.user.profile;
    }
    catch (error) {
        console.error(error);
    }
}

async function postStatusMessage(client, channelId, userId, msg, prefix = "ERROR :") {
    const msgText = prefix ? prefix + msg : msg;
    if (channelId) {
        await client.chat.postEphemeral({
            channel: channelId,
            user: userId,
            text: msgText
        })
    } else {
        await client.chat.postMessage({
            channel: userId,
            text: msgText
        });
    }
    return true;
}

/** Start Bolt App */
(async () => {
    try {
        await app.start(process.env.PORT || 3000);
        console.log('⚡️ Bolt app is running! ⚡️');
    } catch (error) {
        console.error('Unable to start App', error);
    }
})();
filmaj commented 1 year ago

@anilskalyane I am glad I asked the question as I see two problems:

If I am missing some aspect of your requirements where you believe you need to leverage both authorize and installationStore, please let me know. If the documentation was unclear on this topic, that is also helpful to know!

filmaj commented 1 year ago

I also noticed that in your latest code, you no longer define any customRoutes. Let's try to create a reproduction case with a single set of application options. Changing the options around makes this a more difficult task.

anilskalyane commented 1 year ago

It worked 🤯 and thank you for the quick response and help. 🙌

github-actions[bot] commented 1 year ago

👋 It looks like this issue has been open for 30 days with no activity. We'll mark this as stale for now, and wait 10 days for an update or for further comment before closing this issue out. If you think this issue needs to be prioritized, please comment to get the thread going again! Maintainers also review issues marked as stale on a regular basis and comment or adjust status if the issue needs to be reprioritized.

github-actions[bot] commented 1 year ago

As this issue has been inactive for more than one month, we will be closing it. Thank you to all the participants! If you would like to raise a related issue, please create a new issue which includes your specific details and references this issue number.