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

Downloading File Returns HTML #285

Closed Mister-Link closed 6 months ago

Mister-Link commented 6 months ago

Apologies in advance: this is my 2nd submitted issue on Github. Also, consider this me begging for a files.download web API method.

My goal is to download https://files.slack.com/files-pri/BLAH-F06L7BQ8J67/test.txt to my local machine. I'm working on an app in Slack SDK (Deno) via CLI. Creating apps from CLI provides a "xoxe.xoxp" token in ~/.slack/credentials.json (Mac OS), which doesn't seem to work for downloading files. As an alternative, I tried creating a second app manually (https://api.slack.com/apps) which provides the "xoxb" token others advise using, but doesn't seem to succeed in downloading the file either.

url_private, url_private_download, permalink, permalink_public - all of them return HTML.

While I expect requests for contents of file to return plain text for the above direct link, the returned data is always HTML. Depending on my tinkering, response is one of these two (but never actual .txt content):

<a href="https://blah.slack.com/?redir=%2Ffiles-pri%BLAH-F06L7BQ8J67%2Fdownload%2Ftest.txt">Found</a>.

or (truncated):

<!DOCTYPE html><html lang="en-US" class="" data-primer><head><link href="https://a.slack-edge.com/d5fba4c/marketing/style/onetrust/onetrust_banner.css" rel="stylesheet" type="text/css" onload="window._cdn ? _cdn.ok(this, arguments) : null" onerror="window._cdn ? _cdn.failed(this, arguments) : null" crossorigin="anonymous"><link href="https://a.slack-edge.com/e06451a/style/libs/lato-2-compressed.css" rel="stylesheet" type="text/css" onload="window._cdn ? _cdn.ok(this, arguments) : null" onerror="window._cdn ? _cdn.failed(this, arguments) : null" crossorigin="anonymous"><link href="https://a.slack-edge.com/css/v5/style/_generic.typography.larsseit.85ad0e0bbe61bdbf62bdd9efa15a921e01033c37.css" rel="stylesheet" type="text/css" onload="window._cdn ? _cdn.ok(this, arguments) : null" onerror="window._cdn ? _cdn.failed(this, arguments) : null" crossorigin="anonymous"><link rel="canonical" href="https://slack.com"> [...]

I suspect the problem lies with the token I'm using, or a configuration issue on the app, or me misunderstanding how Slack CLI or SDK is intended to operate. Hopefully you can help.

I've read through these issues:

Here's the script that outputs the latter HTML (I get the same whether it's requests.get, curl, or fetch, also tried with Postman):

import requests
headers = {"Authorization": "Bearer xoxb-blah"}
r = requests.get("https://files.slack.com/files-pri/BLAH-F06L7BQ8J67/test.txt", headers=headers, stream=True)
if r.status_code == 200:
    with open("/Users/myuser/Downloads/test.txt", 'wb') as f:
        for chunk in r.iter_content(1024):
            f.write(chunk)

I've confirmed that the app has scopes for file read/write: image

srajiang commented 6 months ago

Hi @Mister-Link! Since you're using the Deno SDK, I'm going to transfer this issue over there first.

srajiang commented 6 months ago

@Mister-Link 👋 Thanks for the detailed description of what you're trying to do, and what you've tried already. Super helpful, and I'm going to try to address everything in an effort to be holistic and give context.

Creating apps from CLI provides a "xoxe.xoxp" token in ~/.slack/credentials.json (Mac OS), which doesn't seem to work for downloading files.

Correct, this token is simply allowing the CLI permission to manage apps on behalf of the authorizing user.

As an alternative, I tried creating a second app manually (https://api.slack.com/apps) which provides the "xoxb" token others advise using, but doesn't seem to succeed in downloading the file either.

Yes that wouldn't exactly work since the app you created with the CLI and the app you set up manually to get the xoxb are different apps (they should have different app IDs, run slack app list and compare to the app ID of the app in the api.slack.com).

But speaking of that second manually created app.. You shared a screenshot of the scopes page with both bot and user scopes. Something you should be aware of is that as of today, Deno Slack SDK apps created with Slack CLI don't support user scopes. You may or may not need those additional scopes, and that impacts what I'd advise you to do 👇

So let's backtrack a little bit to what you're trying to do.

url_private, url_private_download, permalink, permalink_public - all of them return HTML.

Check that your token has the access needed. Using your token, try making a https://api.slack.com/methods/files.info/test call with a file ID. Your screenshot says you've added the scope files:read but maybe you need to reinstall the app again after adding scopes. You can verify all that against the token. I

If that works properly and you can get file info, then my best bet is that your HTTP request is somehow malformed. Some of the responders in the issues you linked mentioned that coming up as an issue.

Hope that helps!

Mister-Link commented 6 months ago

that wouldn't exactly work since the app you created with the CLI and the app you set up manually to get the xoxb are different apps

Drats. Sounds like the SDK only accepts tokens associated with its own app. The above screenshot of scopes is for a different app, so it won't work here. However, the app manifest for what I'm working on includes appropriate scopes:

image

So let's backtrack a little bit to what you're trying to do.

The Slack app is intended to collect user input (including file attachments) in an interactive form, then send it to a ServiceNow endpoint to create an incident. This works - except for sending the file, since I can't find an appropriate token (I know this is the case because I tried with browser cookie and that worked). Not having user scope shouldn't be a problem, since the bot was fed the file via the form.

Check that your token has the access needed.

I suppose that's the root of the issue. I need an xoxb token for this particular app - another app's xoxb token isn't sufficient. credentials.json only contains the xoxp token, and apps created via CLI can only be configured via CLI. Documentation doesn't suggest a way for me to get xoxb token from CLI, and I can't see the xoxb token for the app created with CLI/SDK from front-end (the tester app in below image was created from front-end and has xoxb token, by comparison):

image

I also tried redeploying the app (and reinstalling Slack CLI), but to no avail.

I sure hope this isn't a case where I have to jump to Bolt, since the app is already 99% completed.. At any rate, I'm willing to pursue this a bit further. I'll work on a simplified app that replicates the issue I'm having. I'll try to report back with that within 2 days.

Feel free to ask me to try anything else in the meantime. Thanks for lending your expertise, by the way - I really appreciate you taking the time.

Mister-Link commented 6 months ago

Below is a simplified version for troubleshooting.

manifest.ts has the two variables (your workspace URL and token) and steps to test. Upon submitting workflow with attachment, we expect the console to show the file's contents (I included test.txt in the files, which should output "It works!" in the console). If I set the headers in get_file.ts to Cookie with string from browser, output is "It works!" as I expect. But I can't find an equivalent xoxb token to make this call succeed.

If you find a way to make output show "It works!", problem solved!

simplified-app.zip

seratch commented 6 months ago

When you want to fetch only the file content, you can use url_private_download URL instead.

srajiang commented 6 months ago

Hi @Mister-Link - Really appreciate you taking the time to give context and up a simplified app reproduction! Helped immensely. Give this a try?

simplified-app-works.zip

The only difference here is that I'm destructuring the token property from the SlackFunction context, this xwfp token is all you should need to be able to make this files API call.

Mister-Link commented 6 months ago

the token property from the SlackFunction context

That's it! That's what I've been missing. I wasn't aware that this xwfp token is available as part of SlackFunction. It's in the documentation of course, but I overlooked it:

Xnip2024-02-26_07-16-15

And here's the relevant change (including token in the async call):

export default SlackFunction(
    get_the_file,
  //
  // `token` property here is supplied by the SDK, this is an xwfp (just in time token)
  // different from the `xoxb` or `xoxp` in that it does have an expiration time
  //
  // You can view the full list of function context properties available on SlackFunction here:
  // https://api.slack.com/automation/functions/custom#context
  // 
    async ({ inputs, client, token }) => {
    try {
        for (let file_id of inputs.attachments) {
            let file_response = await client.files.info({ file: file_id });

      // I used url_private_download here as @seratch suggested, but url_private seemed
      // to be working ok as well when I tested with url_private
            const file_content_response = await fetch(file_response.file.url_private_download, {
                method: "GET",
                headers: {
                    Authorization: `Bearer ${token}`,
                },
            });

I'm sure this will help several others immensely; thanks so much for taking the time to research this. I would have surely given up if you hadn't intervened - no chance I would have realized that properties includes a just-in-time token.

I'll close the issue now - thanks again!

srajiang commented 6 months ago

@Mister-Link Yay! I'm very glad this unblocked you :) We're working on getting some more documentation up about the xwfp as well, so that it's a little easier for folks to self-serve on this.