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

Error in Oauth with ExpressReceiver, AWSLambdaReceiver and aws-serverless-express #1074

Closed
5 of 10 tasks
shubhamAffinidi opened this issue Aug 23, 2021 · 7 comments
Closed
5 of 10 tasks
Labels
question M-T: User needs support to use the project

Comments

@shubhamAffinidi
Copy link

shubhamAffinidi commented Aug 23, 2021

### Description

I have been working on the slack app deployment and have followed the steps mentioned in the Issue-784 . In local when we use the ngrok tunnel it works correctly since we have the '/slack/install' url as mentioned in the docs 'NOTE: The Add to Slack button on your app’s app configuration page will not work with Bolt’s built-in OAuth support. The app configuration Add to Slack button does not include a state value. You must use the slack/install route created by your Bolt application to install the app.'

But after deploying I don't know how to get that url and make it work. Also, when I try the 'Sharable URL' I get the following error ERROR [ERROR] OAuth:InstallProvider:0 Error: redirect url is missing state or code query parameters
which is correct since the shareable URL does not have the state parameter as mentioned in the docs.

So what am I missing here?

Here is how my code looks like
App initialization

// OAuth Flow
export const expressReceiver = new ExpressReceiver({
  signingSecret: envConfig.slackBotSigningToken,
  clientId: envConfig.slackBotClientId,
  clientSecret: envConfig.slackBotClientSecret,
  stateSecret: envConfig.slackBotStateSecret,
  scopes: [
    'channels:history',
    'chat:write',
    'groups:history',
    'incoming-webhook',
    'usergroups:read',
    'users:read',
    'users:read.email',
    'app_mentions:read',
  ],
  installationStore: myInstallationStore,
})

export const awsLambdaReceiver = new AwsLambdaReceiver({
  signingSecret: process.env.PROD_SLACKBOT_SIGNING_TOKEN,
})

new App({
      logLevel: LogLevel.DEBUG,
      receiver: awsLambdaReceiver,
      processBeforeResponse: true,
      authorize: async (source) => {
        try {
          const queryResult = await myInstallationStore.fetchInstallation(source)
          if (queryResult === undefined) {
            throw new Error('Failed fetching data from the Installation Store')
          }
          const authorizeResult: any = {}
          authorizeResult.userToken = queryResult.user.token
          if (queryResult.team !== undefined) {
            authorizeResult.teamId = queryResult.team.id
          } else if (source.teamId !== undefined) {
            authorizeResult.teamId = source.teamId
          }

          if (queryResult.enterprise !== undefined) {
            authorizeResult.enterpriseId = queryResult.enterprise.id
          } else if (source.enterpriseId !== undefined) {
            authorizeResult.enterpriseId = source.enterpriseId
          }

          if (queryResult.bot !== undefined) {
            authorizeResult.botToken = queryResult.bot.token
            authorizeResult.botId = queryResult.bot.id
            authorizeResult.botUserId = queryResult.bot.userId
          }
          console.log(JSON.stringify(authorizeResult))
          return authorizeResult
        } catch (error) {
          throw new Error(error.message)
        }
      },
  }) 
const awsServerlessExpress = require('aws-serverless-express');
const server = awsServerlessExpress.createServer(expressReceiver.app);

module.exports.oauthHandler = (event: any, context: any) => {
  awsServerlessExpress.proxy(server, event, context);
}

module.exports.eventHandler = awsLambdaReceiver.toHandler();

module.exports.handler = async (event: any, context: any, callback: any) => {
  if (event.source === 'serverless-plugin-warmup') {
    return callback(null, 'Lambda is warm!')
  }

  const handler: any = await eventListener.start()
  return handler(event, context, callback)
}

This is my serverless.yml

functions:
  slackRedirectURI:
    handler: src/listener.oauthHandler
    timeout: 12
    events:
      - http:
          path: slack/oauth_redirect
          method: get
    # warmup: true

  slack:
    handler: src/listener.handler
    memorySize: 1028
    timeout: 12
    events:
      - http:
          path: slack/events
          method: post
    warmup: true

plugins:
  - serverless-webpack
  - serverless-offline
  - serverless-dotenv-plugin
  - serverless-plugin-warmup

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

  • bug
  • enhancement (feature request)
  • question
  • documentation related
  • example code related
  • testing related
  • discussion

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

  • I've read and understood the Contributing guidelines and have done my best effort to follow them.
  • I've read and agree to the Code of Conduct.
  • I've searched for any related issues and avoided creating a duplicate issue.

Bug Report

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

Reproducible in:

package version: ^3.5.0

node version: v14.17.4

OS version(s):

Steps to reproduce:

Expected result:

Oauth to work correctly after deployment

Actual result:

Error is mentioned in the description

Attachments:

Logs, screenshots, screencast, sample project, funny gif, etc.

@gitwave gitwave bot added the untriaged label Aug 23, 2021
@seratch seratch added question M-T: User needs support to use the project and removed untriaged labels Aug 23, 2021
@seratch
Copy link
Member

seratch commented Aug 23, 2021

Hi @shubhamAffinidi, thanks for asking the question!

You must use the slack/install route created by your Bolt application to install the app.

This means that you can use https://{your AWS domain}/slack/install instead of the generated URL in the Slack app configuration page. If your app installer starts the OAuth flow from /slack/install URL, the state parameter will be added to the query string. Thus, your serverless.yml should have the path /slack/install as well.

But, to make your app functioning, the following changes are required:

  • Remove AwsLambdaReceiver and use only ExpressReceiver (=pass ExpressReceiver as the receiver argument in App constructor)
  • Add /slack/install path to your serverless.yml

The GitHub issue you referred to might confuse you - I'm sorry about that. I hope this was helpful to you!

@shubhamAffinidi
Copy link
Author

Hi @seratch , Thank you for responding so quickly. This solution works. Thank you so much!

@shubhamAffinidi
Copy link
Author

shubhamAffinidi commented Aug 25, 2021

Hi @seratch, the url that is generated ( /slack/install ) is throwing 'Internal Server Error' message. It was working fine before but now it just throws error.

These are my updated files.
SlackService.ts

// OAuth Flow
export const expressReceiver = new ExpressReceiver({
  signingSecret: envConfig.slackBotSigningToken,
  clientId: envConfig.slackBotClientId,
  clientSecret: envConfig.slackBotClientSecret,
  stateSecret: envConfig.slackBotStateSecret,
  scopes: [
    'channels:history',
    'chat:write',
    'groups:history',
    'incoming-webhook',
    'usergroups:read',
    'users:read',
    'users:read.email',
    'app_mentions:read',
  ],
  installationStore: myInstallationStore,
})

export const awsLambdaReceiver = new AwsLambdaReceiver({
  signingSecret: process.env.PROD_SLACKBOT_SIGNING_TOKEN,
})

export const eventListener = new App({
    logLevel: LogLevel.DEBUG,
    receiver: expressReceiver,
  })

listener.ts

const awsServerlessExpress = require('aws-serverless-express');
const server = awsServerlessExpress.createServer(expressReceiver.app);

module.exports.oauthHandler = async (event: any, context: any) => {
  awsServerlessExpress.proxy(server, event, context);
}

module.exports.handler = async (event: any, context: any, callback: any) => {
  if (event.source === 'serverless-plugin-warmup') {
    return callback(null, 'Lambda is warm!')
  }

  const handler: any = await eventListener.start()
  return handler(event, context, callback)
}

serverless.yml

functions:
  slackRedirectURI:
    handler: src/listener.oauthHandler
    timeout: 12
    events:
      - http:
          path: slack/oauth_redirect
          method: get
      - http:
          path: slack/install
          method: get

  slack:
    handler: src/listener.handler
    memorySize: 1028
    timeout: 12
    events:
      - http:
          path: slack/events
          method: post
    warmup: true

  issueVC:
    handler: src/listener.issueVC
    timeout: 12
    events:
      - sns: issueVC

plugins:
  - serverless-webpack
  - serverless-offline
  - serverless-dotenv-plugin
  - serverless-plugin-warmup

@seratch
Copy link
Member

seratch commented Aug 25, 2021

@shubhamAffinidi

the url that is generated ( /slack/install ) is throwing 'Internal Server Error' message. It was working fine before but now it just throws error.

With the given information, I cannot tell anything helpful. I would recommend checking your CloudWatch logs etc. to figure out the cause of the internal server error.

@shubhamAffinidi
Copy link
Author

Seems like the server was not reflecting changes. Now it's working. Closing this issue.

@AmanKishore
Copy link

@seratch Just to confirm if using serverless we can't use the generated URL in the Slack app configuration page.
We must use https://{your AWS domain}/slack/install?

@seratch
Copy link
Member

seratch commented Mar 17, 2022

@AmanKishore Not only for serverless use cases, we generally recommend going with /slack/install for better security (proper state parameter use).

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

No branches or pull requests

3 participants