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

Unhandled request to /slack/install #2096

Open
zhifengkoh opened this issue Apr 18, 2024 · 3 comments
Open

Unhandled request to /slack/install #2096

zhifengkoh opened this issue Apr 18, 2024 · 3 comments
Labels
auto-triage-stale question M-T: User needs support to use the project

Comments

@zhifengkoh
Copy link

Apologies in advance if this isn't the right place to ask, but I didn't know where else to turn to after a lot of Googling and ChatGPT/Perplexity.

I am trying to setup OAuth for my BoltJS app so that I can prepare my app for distribution on multiple workspaces. I'm not very familiar building with OAuth so the inner workings are not familiar to me. So far, I'm following along the BoltJS documentation, which has been quite easy to follow for the most part. But where the BoltJS documentation says once OAuth is enabled, I should see an automatically rendered App Install page located at the URL path /slack/install, my server is instead giving me a 404 error and that this path is unhandled.

I don't understand what the issue could be because so far my app is very simple: it has only implemented

  • A single slash command that opens a single modal view state app.command
  • The corresponding view submission event app.view
  • A message listener (app.message)

Thank you in advance for your help! My app.js is appended below.

Reproducible in:

The Slack SDK version

"slack/bolt": "^3.17.1"

Node.js runtime version

v20.11.1

OS info

ProductName:		macOS
ProductVersion:		14.1.2
BuildVersion:		23B92
Darwin Kernel Version 23.1.0: Mon Oct  9 21:28:45 PDT 2023; root:xnu-10002.41.9~6/RELEASE_ARM64_T6020

Steps to reproduce:

(Share the commands to run, source code, and project settings)

  1. Start my app using node app.js
  2. Hit the URL path of the Slack Install page that is supposed to be rendered automatically by Bolt (http://my-domain.ngrok-free.app/slack/install)

Expected result:

I was expecting to see the default slack install webpage rendered by Bolt, as mentioned here: https://slack.dev/bolt-js/concepts#authenticating-oauth.

My app is working fine when events and user interactions (with modals, multi-select menus built in Block Kit, etc.) are sending requests to http://my-domain.ngrok-free.app/slack/events. So it's not an issue with my ngrok tunnel or the app itself.

Actual result:

After entering http://my-domain.ngrok-free.app/slack/install in my browser:

  1. I see in my terminal log the following error message:
[INFO]   Unhandled HTTP request (GET) made to /slack/install
  1. And in the ngrok command line (the 404 Not Found for /slack/install):
HTTP Requests                                                                                                                                                                                                                                             
-------------                                                                                                                                                                                                                                             
                                                                                                                                                                                                                                                          
POST /slack/events             200 OK                                                                                                                                                                                                                     
POST /slack/events             200 OK                                                                                                                                                                                                                     
GET  /slack/install            404 Not Found                                                                                                                                                                                                              
GET  /slack/install            404 Not Found                                                                                                                                                                                                              
POST /slack/events             200 OK                                                                                                                                                                                                                     
POST /slack/events             200 OK        

My app.js

const { App } = require('@slack/bolt');
const { FileInstallationStore} = require ('@slack/oauth')

const app = new App({
  token: process.env.SLACK_BOT_TOKEN,
  signingSecret: process.env.SLACK_SIGNING_SECRET,
  clientId: process.env.SLACK_CLIENT_ID,
  clientSecret: process.env.SLACK_CLIENT_SECRET,
  stateSecret: 'my-secret', //Note: This should be randomly generated and stored/rotated as an environment variable in production
  scopes: ['chat:write', 'commands'],
  installationStore: new FileInstallationStore()
});

//Retrieve the names of conversations or users given their Slack conversation IDs
async function getNames(client, ids) {
  const names = [];

  for (const id of ids) {
    try {
      if (id.startsWith('U')) {
        // Handle user IDs
        const result = await client.users.info({ user: id });
        if (result.ok) {
          names.push(result.user.real_name); // or `result.user.name` for the username
        } else {
          names.push(`Failed to fetch details for user ID: ${id}`);
        }
      } else {
        // Handle conversation IDs
        const result = await client.conversations.info({ channel: id });
        if (result.ok) {
          names.push(result.channel.name);
        } else {
          names.push(`Failed to fetch details for conversation ID: ${id}`);
        }
      }
    } catch (error) {
      console.error(`Error fetching details for ID: ${id}`, error);
      names.push(`Error for ID: ${id}`);
    }
  }

  return names;
}

//Open a Modal window for the command /alertbot-create
app.command('/alertbot-create', async ({ ack, body, client }) => {
  // Acknowledge the command request
  await ack();
  console.log("command invoked: alertbot-create");

  try {
    // Call views.open with the built-in client
    const result = await client.views.open({
      // Pass a valid trigger_id within 3 seconds of receiving the command
      trigger_id: body.trigger_id,
      // View payload
      view: {
        "type": "modal",
        "callback_id": "alertbot_create_view",
        "title": {
          "type": "plain_text",
          "text": "Create a new Alert"
        },
        "submit": {
          "type": "plain_text",
          "text": "Submit"
        },
        "blocks": [
          {
            "type": "input",
            "block_id": "search_phrase_block",
            "element": {
              "type": "plain_text_input",
              "action_id": "search_phrase_action",
              "multiline": false,
              "placeholder": {
                "type": "plain_text",
                "text": "The exact word or phrase that you want to listen out for"
              }
            },
            "label": {
              "type": "plain_text",
              "text": "Search Phrase"
            }
          },
          {
            "type": "input",
            "block_id": "alert_message_contents_block",
            "element": {
              "type": "plain_text_input",
              "multiline": true,
              "action_id": "alert_message_contents_action",
              "placeholder": {
                "type": "plain_text",
                "text": "The contents of your alert message, excluding users or groups"
              }
            },
            "label": {
              "type": "plain_text",
              "text": "Alert Message Contents",
              "emoji": true
            }
          },
          {
            "type": "input",
            "block_id": "recipients_block",
            "element": {
              "type": "multi_conversations_select",
              "placeholder": {
                "type": "plain_text",
                "text": "Select conversations"
              },
              "filter": {
                "include": [
                  "public",
                  "private",
                  "im"
                ],
                "exclude_bot_users": true
              },
              "action_id": "recipients_action",
              "default_to_current_conversation": true
            },
            "label": {
              "type": "plain_text",
              "text": "Select users to @mention and/or #channels to post to"
            }
          },
          {
            "type": "input",
            "block_id": "alert_configuration_block",
            "element": {
              "type": "checkboxes",
              "options": [
                {
                  "text": {
                    "type": "plain_text",
                    "text": "Send alert as DM only"
                  },
                  "description": {
                    "type": "mrkdwn",
                    "text": "Alerts will be sent from AlertBot directly to users mentioned. Channels will be ignored."
                  },
                  "value": "is_dm_only"
                },
                {
                  "text": {
                    "type": "plain_text",
                    "text": "Disable threaded alerts"
                  },
                  "description": {
                    "type": "mrkdwn",
                    "text": "For alerts posted as a response to the trigger message in its original channel, do not reply in a thread."
                  },
                  "value": "disable_threading"
                },
                {
                  "text": {
                    "type": "plain_text",
                    "text": "Disable link to trigger message"
                  },
                  "description": {
                    "type": "mrkdwn",
                    "text": "For alerts posted elsewhere besides the original channel, do not include a link to the triggering message."
                  },
                  "value": "disable_link_to_trigger"
                }
              ],
              "action_id": "alert_configuration_action"
            },
            "label": {
              "type": "plain_text",
              "text": "Alert configuration",
              "emoji": true
            }
          }
        ]
      }
    });
  } catch (error) {
    console.error(error);
  }
});

//Listen to user selection of @users or #channels in the multi conversations select menu of the alertbot-create modal
app.action('multi_conversations_select-action', async({ack, body, client}) => {
  await ack();
  console.log("action captured: multi_conversations_select-action");

});

//Listen to the Modal window submission for /alertbot-create
app.view('alertbot_create_view', async ({ ack, body, view, client}) => {
  console.log("view submitted: alertbot_create_view");

  // Check if at least one conversation is selected in the recipients_action multi conversations select menu
  const selectedConversations = view.state.values.recipients_block.recipients_action.selected_conversations;
  if (!selectedConversations || selectedConversations.length === 0) {
    // Acknowledge the view submission with an error if no conversations are selected
    return await ack({
        response_action: 'errors',
        errors: {
            recipients_action: 'Please select at least one conversation.'
        }
    });
}

  // Acknowledge the view submission event
  await ack();

  const user_id = body.user.id;
  const alert_search_phrase = view.state.values.search_phrase_block.search_phrase_action.value;
  const alert_message_contents = view.state.values.alert_message_contents_block.alert_message_contents_action.value;
  const selected_config_options = view.state.values.alert_configuration_block.alert_configuration_action.selected_options;  

  try {
    //Retrieve conversation names from conversation IDs
    const names = await getNames(client, selectedConversations);
    console.log("Conversation names:", names);

    //Extract alert configuration options
    const configOptions = selected_config_options.map(option => option.value);
    console.log(configOptions);

    await client.chat.postMessage({
      channel: user_id,
      text: "You created a new AlertBot alert",
      blocks: [
        {
          "type": "section",
          "text": {
            "type": "mrkdwn",
            "text": "⚠️ *You created a new alert*"
          }
        },
        {
          "type": "section",
          "fields": [
            {
              "type": "mrkdwn",
              "text": "*Search Phrase*"
            },
            {
              "type": "plain_text",
              "text": alert_search_phrase
            },
            {
              "type": "mrkdwn",
              "text": "*Alert Message Contents*"
            },
            {
              "type": "plain_text",
              "text": alert_message_contents
            },
            {
              "type": "mrkdwn",
              "text": "*Recipients*"
            },
            {
              "type": "plain_text",
              "text": names.join(", ")
            },
            {
              "type": "mrkdwn",
              "text": "*Configuration Options*"
            },
            {
              "type": "plain_text",
              "text": configOptions.join(", ")
            }
          ]
        }
      ]
    });
  } catch (error) {
    console.error(error);
  }

});

// Listen for messages containing "ping" and respond
app.message('ping', async ({ message, say }) => {
  console.log("messge event: specific string");
  try {
    await say({
      text: `Hello <@${message.user}>!`,
      blocks: [
        {
          type: "section",
          text: {
            type: "mrkdwn",
            text: `Hey <@${message.user}>, check this out!`
          },
          accessory: {
            type: "button",
            text: {
              type: "plain_text",
              text: "Click me!"
            },
            url: "https://example.com"
          }
        }
      ]
    });
  } catch (error) {
    console.error(error);
  }
});

(async () => {
  let port = process.env.PORT;
  if (port == null || port == "") {
    port = 8000;
  }
  // app.listen(port);
  await app.start(process.env.PORT || 3000);
  console.log('Slack app is running on port ' + (process.env.PORT || 3000));

})();
@seratch seratch added question M-T: User needs support to use the project and removed untriaged labels Apr 18, 2024
@seratch
Copy link
Member

seratch commented Apr 18, 2024

Hi @zhifengkoh, thanks for asking the question. I just quickly checked if your example app works for me, and I didn't see any issues with it (token: process.env.SLACK_BOT_TOKEN, is unnecessary though). http://localhost:300/slack/install services the page as I expect. I guess your ngrok might be forwarding your browser request to a different process or your changes to enable the OAuth flow might not be reflected to the running process. I hope you will figure the cause out soon.

@zhifengkoh
Copy link
Author

Thanks @seratch for helping me to verify that! I will try to figure it out.

Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
auto-triage-stale question M-T: User needs support to use the project
Projects
None yet
Development

No branches or pull requests

2 participants