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

Not getting `challenge` from Enable Events tool #1135

Closed jp555soul closed 3 years ago

jp555soul commented 3 years ago

Description

Moving bot from socket mode to HTTP events for release. When inputting my URL, the app shows that it iss not sending any challenge data. Nor am I seeing any in the request logs.

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.4.0

node version: 15.x

OS version(s): OSX 11.5.1

Steps to reproduce:

  1. Turn on server with logs
  2. Fill out form with URL
  3. Wait for response

Expected result:

POST response with data.

Actual result:

POST response with no data.

Attachments:

Screen Shot 2021-09-27 at 5 56 00 PM

misscoded commented 3 years ago

Hi @jp555soul! This definitely threw me at first: I've never seen a case where the token and challenge are both missing entirely.

I tried to reproduce your situation, beginning with a Socket Mode-enabled app and switching to HTTP. For the sake of ease, I used ngrok as a tunnel, though the result should be the same if I were deploying to Heroku and a successful connection was made.

After switching out the Socket Mode-specific properties (appToken, socketMode: true) in the App instantiation for non-Socket Mode ones (token, signingSecret), and restarting the server, all worked as expected and the verification succeeded.

Can you confirm on the Heroku end that the updated App code with the non-Socket Mode credentials is what's currently being served from that new Request URL?

jp555soul commented 3 years ago

@misscoded Thanks for your response. I've gone ahead and updated the code (posted below) as well as pointed it to a new route to confirm an update, but I'm still getting the same results. Code and screenshot attached below.

Code

require('dotenv').config();
const massive = require('massive')
const express = require('express')
const config = require('../config')
const { InstallProvider } = require('@slack/oauth');
const { App, ExpressReceiver, LogLevel } = require('@slack/bolt');
const uuid = require("uuid");
const packageJson = require('../package.json');

const app = express();
var http = require('http').Server(app);

//LOGGING
let agent = undefined;
const developerMode = true;
const logLevel = process.env.SLACK_LOG_LEVEL || LogLevel.DEBUG;

massive(config.database).then(async i => {
  // Start your app
  app.set('db', i);
  console.log('Connected to DB');
  http.listen(config.app.port, config.app.host, async () => {
    console.log(`Server running on ${config.app.host}:${config.app.port}`)
  });
}).catch(err => {
  console.log("Unable to instantiate database, ", err);
});

const installer = new InstallProvider({
  clientId: process.env.SLACK_CLIENT_ID,
  clientSecret: process.env.SLACK_CLIENT_SECRET,
  stateSecret: process.env.STATE_SECRET,
  authVersion: 'v2',
  //logLevel,
  installationStore: {
    storeInstallation: async (installation) => {
      console.log('storeInstallation -- installer')
      const db = app.get('db');
      // change the line below so it saves to your database
      if (installation.isEnterpriseInstall && installation.enterprise !== undefined) {
        // support for org wide app installation
        let entAccount = await db.table.findOne({ enterprise: installation.enterprise.id });
        if (entAccount) {
          const criteria = {
            enterprise: installation.enterprise.id
          }

          let changes;

          changes = {
            'token': installation['bot']['token'],
            'tokenraw': JSON.stringify(installation)
          }

          return await db.table.update(criteria, changes)
          .then(success => {
            console.log("The app was installed successfully. ENT");
          }).catch(err => { console.log(err) });
        } else {
          return await db.table.insert({
            enterprise: installation.enterprise.id,
            token: installation['bot']['token'],
            tokenraw: JSON.stringify(installation)
          })
          .then(success => {
            console.log("The app was installed successfully. ENT");
          }).catch(err => { console.log(err) });
        }
      }
      if (installation.team !== undefined) {
        // single team app installation
        let teamAccount = await db.table.findOne({ team: installation.team.id });
        if (teamAccount) {
          const criteria = {
            team: installation.team.id
          }

          let changes;

          changes = {
            'token': installation['bot']['token'],
            'tokenraw': JSON.stringify(installation)
          }

          return await db.table.update(criteria, changes)
          .then(success => {
            console.log("The app was installed successfully. TEAM");
          }).catch(err => { console.log(err) });
        } else {
          return await db.table.insert({
            team: installation.team.id,
            token: installation['bot']['token'],
            tokenraw: JSON.stringify(installation)
          })
          .then(success => {
            console.log("The app was installed successfully. TEAM");
          }).catch(err => { console.log(err) });
        }
      }
      throw new Error('Failed saving installation data to installationStore');
    },
  },
  installerOptions: {
    installPath: 'slack/install',
    redirectUriPath: 'slack/oauth_redirect',
  }
});

const bolt = new App({
  signingSecret: process.env.SLACK_SIGNING_SECRET,
  clientId: process.env.SLACK_CLIENT_ID,
  clientSecret: process.env.SLACK_CLIENT_SECRET,
  stateSecret: process.env.STATE_SECRET,
  //token: process.env.SLACK_APP_TOKEN,
  //socketMode: true,
  //agent,
  //logLevel,
  //developerMode,
  stateVerification: false,
  installationStore: {
    storeInstallation: async (installation) => {
      console.log('storeInstallation -- bolt')
      //console.log(installation)
      const db = app.get('db');
      // change the line below so it saves to your database
      if (installation.isEnterpriseInstall && installation.enterprise !== undefined) {
        // support for org wide app installation
        let entAccount = await db.table.findOne({ enterprise: installation.enterprise.id });
        if (entAccount) {
          const criteria = {
            enterprise: installation.enterprise.id
          }

          let changes;

          changes = {
            'token': installation['bot']['token'],
            'tokenraw': JSON.stringify(installation)
          }

          return await db.table.update(criteria, changes)
        } else {
          return await db.table.insert({
            enterprise: installation.enterprise.id,
            token: installation['bot']['token'],
            tokenraw: JSON.stringify(installation)
          });
        }
      }
      if (installation.team !== undefined) {
        // single team app installation
        let teamAccount = await db.table.findOne({ team: installation.team.id });
        if (teamAccount) {
          const criteria = {
            team: installation.team.id
          }

          let changes;

          changes = {
            'token': installation['bot']['token'],
            'tokenraw': JSON.stringify(installation)
          }

          return await db.table.update(criteria, changes)
        } else {
          return await db.table.insert({
            team: installation.team.id,
            token: installation['bot']['token'],
            tokenraw: JSON.stringify(installation)
          })
          .then(success => {
            console.log("The app was installed successfully.");
          }).catch(err => { console.log(err) });
        }
      }
      throw new Error('Failed saving installation data to installationStore');
    },
    fetchInstallation: async (installQuery) => {
      console.log('fetchInstallation - BOLT')
      const db = app.get('db');
      // change the line below so it fetches from your database
      if (installQuery.isEnterpriseInstall && installQuery.enterpriseId !== undefined) {
        // org wide app installation lookup
        return await db.table.findOne({
          enterprise: installQuery.enterpriseId
        });
      }
      if (installQuery.teamId !== undefined) {
        // single team app installation lookup
        let data = await db.table.findOne({ team: installQuery.teamId });
        let token = JSON.parse(data.tokenraw);
        return token;
      }
      throw new Error('Failed fetching installation');
    },
    deleteInstallation: async (installQuery) => {
      console.log('deleteInstallation - BOLT')
      const db = app.get('db');
      // change the line below so it deletes from your database
      if (installQuery.isEnterpriseInstall && installQuery.enterpriseId !== undefined) {
        // org wide app installation deletion
        return await db.table.destroy({
          enterprise: installQuery.enterpriseId
        });
      }
      if (installQuery.teamId !== undefined) {
        // single team app installation deletion
        return await db.table.destroy({
          team: installQuery.teamId
        });
      }
      throw new Error('Failed to delete installation');
    },
  },
  installerOptions: {
    installPath: '/slack/install',
    redirectUriPath: '/slack/oauth_redirect',
  }
});

bolt.event('app_home_opened', async ({ event, client }) => {
  try {
    // Call views.publish with the built-in client
    const result = await client.views.publish({
      // Use the user ID associated with the event
      user_id: event.user,
      view: {
        // Home tabs must be enabled in your app configuration page under "App Home"
        "type": "home",
        "blocks": [{
            "type": "section",
            "text": {
              "type": "mrkdwn",
              "text": "*Welcome <@" + event.user + "> :hand: :rocket:*"
            }
          },
          {
            "type": "section",
            "text": {
              "type": "mrkdwn",
              "text": "New features coming soon."
            }
          }
        ]
      }
    });
  } catch (error) {
    console.error(error);
  }
});

bolt.event("app_mention", async ({ logger, event, say }) => {
  //logger.debug("app_mention event payload:\n\n" + JSON.stringify(event, null, 2) + "\n");
  const result = await say({
    text: `:wave: <@${event.user}> Hi there!`,
  });
  //logger.debug("say result:\n\n" + JSON.stringify(result, null, 2) + "\n");
});

app.get('/', async (req, res) => {
  res.redirect('slack/install');
});

app.get('/slack/install', async (req, res, next) => {
  try {
    // feel free to modify the scopes
    const genUrl = await installer.generateInstallUrl({
      scopes: [
        'channels:read',
        'app_mentions:read',
        'channels:history',
        'channels:join',
        'chat:write',
        'commands',
        'emoji:read',
        'groups:history',
        'im:history',
        'incoming-webhook',
        'mpim:history',
        'reactions:write',
        'conversations.connect:manage',
        'chat:write.public',
        'conversations.connect:read',
        'conversations.connect:write',
        'im:read',
        'im:write',
        'mpim:read',
        'mpim:write',
      ],
    })

    res.send(`<a href=${genUrl}><img alt=""Add to Slack"" height="40" width="139" src="https://platform.slack-edge.com/img/add_to_slack.png" srcset="https://platform.slack-edge.com/img/add_to_slack.png 1x, https://platform.slack-edge.com/img/add_to_slack@2x.png 2x" /></a>`);
  } catch (error) {
    console.log(error)
  }
});

app.get('/slack/oauth_redirect', (req, res) => {
  installer.handleCallback(req, res);
});

app.post('/slack/events', (req, res) => {
  console.log('SLACK EVENTS')
  console.log(req.params)
  console.log(req.body)
  // if (req.body.type === 'url_verification') {
  //   res.send(req.body.challenge);
  // }
  res.send(bolt.receiver.endpoints[0]);
});

app.use('/slack/enable_events', (req, res) => {
  console.log('SLACK ENABLE EVENTS')
  console.log(req.params)
  console.log(req.body)
  // if (req.body.type === 'url_verification') {
  //   res.send(req.body.challenge);
  // }
  res.send(bolt.receiver.endpoints[0]);
});

(async () => {
  await bolt.start(config.app.bolt);
  console.log('⚡️ Bolt app is running!');
})();

Screenshot

Screen Shot 2021-09-28 at 2 47 49 PM

misscoded commented 3 years ago

Thanks for providing this code sample -- super helpful to be able to work from!

I ran your implementation locally (though commented out the DB-related items for testing purposes). The only thing I needed to change to get a successful verification was the port value being used for bolt.start(config.app.bolt). Since I have no visibility into what your value is, I replaced it to align with my own local implementation, which happened to be 3000.

Our Heroku deployment guide links out to this documentation regarding selecting the correct port, which might be worth a glance if you haven't already seen it.

Can you verify that the port value assigned to config.app.bolt matches what is expected on the Heroku side?

jp555soul commented 3 years ago

@misscoded Thanks for the assist. It ended up being a port issue with Heroku and I ended up moving to have the app completely in Bolt with it working.