slackapi / deno-slack-sdk

SDK for building Run on Slack apps using Deno
https://api.slack.com/automation
MIT License
155 stars 27 forks source link

[BUG] Oauth2 with linear.app is broken #259

Open NickeZ opened 8 months ago

NickeZ commented 8 months ago

The deno-slack versions

$ cat import_map.json | grep deno-slack
    "deno-slack-sdk/": "https://deno.land/x/deno_slack_sdk@2.4.1/",
    "deno-slack-api/": "https://deno.land/x/deno_slack_api@2.1.2/",

Deno runtime version

$ deno --version
deno 1.38.5 (release, aarch64-apple-darwin)
v8 12.0.267.1
typescript 5.2.2

OS info

$ sw_vers && uname -v
ProductName:            macOS
ProductVersion:         14.0
BuildVersion:           23A344
Darwin Kernel Version 23.0.0: Fri Sep 15 14:42:57 PDT 2023; root:xnu-10002.1.13~1/RELEASE_ARM64_T8112

Describe the bug

I'm trying to add linear.app oauth2 api to a slack app as an "external-auth". Whenever I try to authentiecate I get an error message "unable_to_parse_state", see attached image. I did double check that the state parameter has the same content before the redirect as after. Slack support told me to reach out here.

Screenshot 2023-11-25 at 10 53 17

Steps to reproduce

  1. Create a new slack app using the CLI.
  2. Create an oauth2 app over at linear.app.
  3. Add the oauth2 app as an external auth provider in slack. (https://api.slack.com/automation/external-auth)
  4. Try authenticating

Expected result

I expect the authentication to work

Actual result

Error message about parsing state

Requirements

Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you are agreeing to those rules.

WilliamBergamin commented 8 months ago

Hi @NickeZ thanks for writing in 💯

Does this consistently happen?

I might be out of my depth here but this error may indicate that the OAuth protocol may not be properly implemented (I have not tested this out yet)

In short based on the OAuth2 protocol if the Authorization Request sent by Slack includes a state parameter then the Authorization Response returned by the linear.app api is required to contain this state parameter as well. The error message leads me to believe that this state parameter may be invalid in the response.

Regardless this feels like a server side issue, I've inquired internally about this

NickeZ commented 8 months ago

Hi @NickeZ thanks for writing in 💯

Does this consistently happen?

Yes, it has happened consistently over a couple of weeks while chatting with slack support.

I might be out of my depth here but this error may indicate that the OAuth protocol may not be properly implemented (I have not tested this out yet)

This might be the case. I did try setting up a simple oauth2 integration using a rust app. Using that I had no problem authenticating.

In short based on the OAuth2 protocol if the Authorization Request sent by Slack includes a state parameter then the Authorization Response returned by the linear.app api is required to contain this state parameter as well. The error message leads me to believe that this state parameter may be invalid in the response.

The state parameter is correctly sent back and forth. I double checked the URLs and it doesn't change. The value of the state parameter set by slack is quite long. Maybe that contributes to some problem? Maybe the value is truncated by some proxy at slack?

Regardless this feels like a server side issue, I've inquired internally about this

I think the error message is quite vague and I have a hard time understanding exactly where the integration fails.

WilliamBergamin commented 7 months ago

Hi @NickeZ

Where there OAuth flows with other providers on this workspace that where successful?

Any chance you could join the Slack Community workspace in order to exchange your specific environment information and track down these logs?

WilliamBergamin commented 7 months ago

Hi @NickeZ would you be able to share what the provider configuration looks like? And share what the raw manifest looks like by pasting the output of the slack manifest command? 🙏

NickeZ commented 7 months ago

Hi,

provider config:

mport { DefineOAuth2Provider, Manifest, Schema } from "deno-slack-sdk/mod.ts";

import { CreateLinearIssueFunction } from "./functions/create_linear_issue.ts";

const LinearProvider = DefineOAuth2Provider({
  provider_key: "linear",
  provider_type: Schema.providers.oauth2.CUSTOM,
  options: {
    provider_name: "Linear",
    authorization_url: "https://linear.app/oauth/authorize",
    token_url: "https://api.linear.app/oauth/token",
    client_id: "084...",
    scope: [
      "read",
      "issues:create",
    ],
    authorization_url_extras: {
      prompt: "consent",
    },
    identity_config: {
      url: "https://api.linear.app/graphql",
      account_identifier: "$.data.viewer.email",
      http_method_type: "POST",
      headers: { "Content-type": "application/json" },
      body: { "query": "{ viewer { email } }" },
    },
    use_pkce: false,
  },
});

/**
 * The app manifest contains the app's configuration. This
 * file defines attributes like app name and description.
 * https://api.slack.com/future/manifest
 */
export default Manifest({
  name: "linear-slack-integration",
  description: "Create issues in linear",
  icon: "assets/default_new_app_icon.png",
  functions: [CreateLinearIssueFunction],
  workflows: [],
  outgoingDomains: ["api.linear.app", "linear.app"],
  botScopes: ["commands", "chat:write", "chat:write.public"],
  externalAuthProviders: [LinearProvider],
});

slack manifest:

{
  "_metadata": {
    "major_version": 2
  },
  "display_information": {
    "name": "linear-slack-integration",
    "description": "Create issues in linear"
  },
  "features": {
    "app_home": {
      "messages_tab_enabled": true,
      "messages_tab_read_only_enabled": true
    },
    "bot_user": {
      "display_name": "linear-slack-integration"
    }
  },
  "oauth_config": {
    "scopes": {
      "bot": [
        "commands",
        "chat:write",
        "chat:write.public"
      ]
    },
    "token_management_enabled": false
  },
  "settings": {
    "org_deploy_enabled": true,
    "incoming_webhooks": {},
    "function_runtime": "slack",
    "siws_links": {}
  },
  "functions": {
    "create_linear_issue": {
      "title": "Create Linear Issue",
      "description": "",
      "input_parameters": {
        "properties": {
          "team_identifier": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "user": {
            "type": "string"
          },
          "linearAccessTokenId": {
            "type": "slack#/types/credential/oauth2",
            "oauth2_provider_key": "linear"
          }
        },
        "required": [
          "team_identifier",
          "title",
          "description",
          "user"
        ]
      },
      "output_parameters": {
        "properties": {
          "result": {
            "type": "string"
          },
          "issue_url": {
            "type": "string"
          }
        },
        "required": [
          "result"
        ]
      }
    }
  },
  "outgoing_domains": [
    "api.linear.app",
    "linear.app"
  ],
  "external_auth_providers": {
    "oauth2": {
      "linear": {
        "provider_type": "CUSTOM",
        "options": {
          "provider_name": "Linear",
          "authorization_url": "https://linear.app/oauth/authorize",
          "token_url": "https://api.linear.app/oauth/token",
          "client_id": "08....",
          "scope": [
            "read",
            "issues:create"
          ],
          "authorization_url_extras": {
            "prompt": "consent"
          },
          "identity_config": {
            "url": "https://api.linear.app/graphql",
            "account_identifier": "$.data.viewer.email",
            "http_method_type": "POST",
            "headers": {
              "Content-type": "application/json"
            },
            "body": {
              "query": "{ viewer { email } }"
            }
          },
          "use_pkce": false
        }
      }
    }
  }
}
WilliamBergamin commented 7 months ago

@NickeZ we've published a connector that successfully executes the hand-shack

The linear provider config looks something like this

{
  "provider_type": "CUSTOM",
  "options": {
    "client_id": "xxxx",
    "scope": [
      "read",
      "write",
      "issues:create",
      "comments:create"
    ],
    "provider_name": "Linear",
    "authorization_url": "https://linear.app/oauth/authorize",
    "token_url": "https://api.linear.app/oauth/token",
    "identity_config": {
      "url": "https://api.linear.app/graphql",
      "body": {
        "query": "{ viewer { id email } }"
      },
      "http_method_type": "POST",
      "headers": {
        "Content-Type": "application/json"
      },
      "account_identifier": "$.data.viewer.email"
    }
  }
}

Similar to your implementation, but it seems like authorization_url_extras is not required, could you try removing it from your provider

NickeZ commented 4 months ago

Hello!

I'm back on this task again. I updated all slack dependencies and now I get a bit further. When I do slack run I see the following in the console: Auth start succeeded by user 'XXXX' on team 'XXXX' for app 'XXXXX' and provider 'linear'. So it looks better. But the final redirect doesn't seem to happen. I'm stuck on https://oauth2.slack.com/external/auth/callback?code=455a8024ff5c31780e2bfd6451cbbe896980fa9c7e624eac66a89f2403f573c9&state=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0ZWFtX2lkIjoiVDAyOFlRNkRCIiwidXNlcl9pZCI6IlUwMlE3MVc2REM3IiwicHJvdmlkZXJfaWQiOjYyODkyMTY3NzY0MTcsImV4cCI6MTcxMzE3NDY3NCwic291cmNlIjoic2xhY2tfY2xpZW50Iiwid29ya2Zsb3dfaWQiOiJXZjA2N1M1VVRHTTYiLCJmdW5jdGlvbl9pZCI6IkZuMDY4NzEyNFlDVSJ9.CtjNl6Ghcyn_Pqrx8EyAmN01-npyzwEasWS_LgrVOB8 in the web browser. The page is just a white blank screen. The same happens if I initiate from the slack macos app or the slack web app. I suspect I should be redirected back to the slack app? I tested with Safari on macos.

NickeZ commented 4 months ago

The slack connector that you have published works fine. But I need the customizability of running my own connector.

NickeZ commented 4 months ago

Tested various browsers:

Screenshot 2024-04-15 at 14 16 19
NickeZ commented 4 months ago

I figured out the "state" problem. It is because I use firefox containers. It opened the first link as a "containerless" tab. But then when I pressed "Continue to linear" it opened a "Work" container. But the other issue still remains. I get a blank screen / 500.

WilliamBergamin commented 4 months ago

Hi @NickeZ thanks for getting back to us,

Could you ensure that options.identity_config.body.query is "{ viewer { id email } }" and could you share the updated output of slack manifest?

NickeZ commented 4 months ago

Yes, options.identity_config.body.query has the right value. I tried adding some more domains to the list of outgoing domains. But no success..

$ slack manifest
{
  "_metadata": {
    "major_version": 2
  },
  "display_information": {
    "name": "linear-slack-integration",
    "description": "Create issues in linear"
  },
  "features": {
    "app_home": {
      "messages_tab_enabled": true,
      "messages_tab_read_only_enabled": true
    },
    "bot_user": {
      "display_name": "linear-slack-integration"
    }
  },
  "oauth_config": {
    "scopes": {
      "bot": [
        "commands",
        "chat:write",
        "chat:write.public"
      ]
    },
    "token_management_enabled": false
  },
  "settings": {
    "org_deploy_enabled": true,
    "incoming_webhooks": {},
    "function_runtime": "slack",
    "siws_links": {}
  },
  "functions": {
    "create_linear_issue": {
      "title": "Create Linear Issue",
      "description": "",
      "input_parameters": {
        "properties": {
          "team_identifier": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "user": {
            "type": "string"
          },
          "linearAccessTokenId": {
            "type": "slack#/types/credential/oauth2",
            "oauth2_provider_key": "linear"
          }
        },
        "required": [
          "team_identifier",
          "title",
          "description",
          "user"
        ]
      },
      "output_parameters": {
        "properties": {
          "result": {
            "type": "string"
          },
          "issue_url": {
            "type": "string"
          }
        },
        "required": [
          "result"
        ]
      }
    }
  },
  "outgoing_domains": [
    "api.linear.app",
    "client-api.linear.app",
    "s.linear.app",
    "linear.app"
  ],
  "external_auth_providers": {
    "oauth2": {
      "linear": {
        "provider_type": "CUSTOM",
        "options": {
          "provider_name": "Linear",
          "authorization_url": "https://linear.app/oauth/authorize",
          "token_url": "https://api.linear.app/oauth/token",
          "client_id": "08...",
          "scope": [
            "write",
            "read",
            "issues:create",
            "comments:create"
          ],
          "identity_config": {
            "url": "https://api.linear.app/graphql",
            "account_identifier": "$.data.viewer.email",
            "http_method_type": "POST",
            "headers": {
              "Content-type": "application/json"
            },
            "body": {
              "query": "{ viewer { id email } }"
            }
          }
        }
      }
    }
  }
}
WilliamBergamin commented 4 months ago

@NickeZ thanks for sharing 💯 from what I can tell your configuration is nearly identical to the one used by the linear connector, since this configuration works for the connector I would recommend reaching out to linear in order to ensure everything is setup properly on their end