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

Cannot download private release asset #12

Open
BotellaA opened this issue Jul 2, 2019 · 38 comments
Open

Cannot download private release asset #12

BotellaA opened this issue Jul 2, 2019 · 38 comments
Labels
Status: Blocked Some technical or requirement is blocking the issue Type: Bug Something isn't working as documented

Comments

@BotellaA
Copy link

BotellaA commented Jul 2, 2019

I am trying to download an asset from Github. Here is the corresponding code:

import Octokit from "@octokit/rest"
const octokit = new Octokit({
  auth: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
});
const owner = "Geode-solutions";
const repo = "opengeode";
const asset_id = 13450175;
octokit
  .request("GET /repos/:owner/:repo/releases/assets/:asset_id", {
      headers: {
         Accept: "application/octet-stream"
      },
      owner,
      repo,
      asset_id
});

I got this error in return:

OPTIONS https://github-production-release-asset-2e65be.s3.amazonaws.com/156866568/4524ae80-99ab-11e9-93db-8c3a783c062c?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20190702%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20190702T120659Z&X-Amz-Expires=300&X-Amz-Signature=65d90d511f7753dde757d77cab65ccea135868dce37c386e6fdb7a52c1ae813e&X-Amz-SignedHeaders=host&actor_id=3213882&response-content-disposition=attachment%3B%20filename%3DOpenGeode-1.5.16-Linux.tar.gz&response-content-type=application%2Foctet-stream 403 (Forbidden)

Access to fetch at 'https://github-production-release-asset-2e65be.s3.amazonaws.com/156866568/4524ae80-99ab-11e9-93db-8c3a783c062c?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20190702%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20190702T120659Z&X-Amz-Expires=300&X-Amz-Signature=65d90d511f7753dde757d77cab65ccea135868dce37c386e6fdb7a52c1ae813e&X-Amz-SignedHeaders=host&actor_id=3213882&response-content-disposition=attachment%3B%20filename%3DOpenGeode-1.5.16-Linux.tar.gz&response-content-type=application%2Foctet-stream' (redirected from 'https://api.github.com/repos/Geode-solutions/opengeode/releases/assets/13450175') from origin 'null' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

What do I have to add to my headers to make it works?
Thanks for your help!

@issue-label-bot

This comment was marked as outdated.

@gr2m
Copy link
Contributor

gr2m commented Jul 2, 2019

I fear it doesn’t work for a similar reason as https://github.com/octokit/rest.js/issues/758, the CORS headers configured on GitHub’s S3 are probably not allowing for direct downloads in browsers from other domains.

Can you try to not set the custom header so that you can retrieve the browser_download_url from the response. Then try to download it using fetch(browser_download_url, { mode: 'no-cors'})

Let me know if that works

@BotellaA
Copy link
Author

BotellaA commented Jul 3, 2019

I tried this code but I am not sure about the Authorization section.

fetch(browser_download_url, {
  mode: "no-cors",
  headers: {
     "Authorization": "Bearer xxxxxxxxx"
  }                      
})

Here is the response

GET https://github.com/Geode-solutions/OpenGeode/releases/download/v1.5.16/OpenGeode-1.5.16-Linux.tar.gz 404 (Not Found)

Response {type: "opaque", url: "", redirected: false, status: 0, ok: false, …}

@gr2m
Copy link
Contributor

gr2m commented Jul 3, 2019

As this is in the browser, would it be a redirection to just redirect to https://github.com/Geode-solutions/OpenGeode/releases/download/v1.5.16/OpenGeode-1.5.16-Linux.tar.gz or whatever browser_download_url returns?

I’ll look more into it, I’m curious where exactly the problem lies, but it’s most likely a problem with CORS settings on Amazon.

Could you explain your use case why you need to download the asset in the browser? I’m trying to build a case to have these CORS headers added, knowing your use case will help :)

@BotellaA
Copy link
Author

BotellaA commented Jul 3, 2019

browser_download_url returns https://github.com/Geode-solutions/OpenGeode/releases/download/v1.5.16/OpenGeode-1.5.16-Linux.tar.gz, so ok on this side.

I am building an electron app with a plugin system. Theses plugins use custom native node modules. These modules are already compiled and released as in a Github release asset (one for each platform: windows, linux, mac). My goal is to automatically download these "ready to go" assets from the plugin.

@gr2m
Copy link
Contributor

gr2m commented Jul 3, 2019

I’m not very familiar with Electron, but I don’t think it should be affected by CORS limitations as its a native app? Could you maybe create a repository with a minimal electron app to reproduce the problem?

And could you test it against a public repository? Does it work there?

@BotellaA
Copy link
Author

BotellaA commented Jul 4, 2019

It works with a public repository. I must have my headers wrong.
I have a personal token, how should I use it in the fetch?

@gr2m
Copy link
Contributor

gr2m commented Jul 4, 2019

In fetch you’d pass the personal access token like this

fetch(url, {
  headers: {
    authorization: 'token <YOUR TOKEN HERE>'
  }
})

@BotellaA
Copy link
Author

BotellaA commented Jul 5, 2019

Ok, so I tried this code

fetch(browser_download_url, {
  headers: {
    authorization: "token xxxxxxxx"
  }
})

Here the response

Response {
  size: 0,
  timeout: 0,
  [Symbol(Body internals)]:
   { body:
      Gunzip {
        _writeState: [Uint32Array],
        _readableState: [ReadableState],
        readable: true,
        _events: [Object],
        _eventsCount: 7,
        _maxListeners: undefined,
        _writableState: [WritableState],
        writable: true,
        allowHalfOpen: true,
        _transformState: [Object],
        _hadError: false,
        bytesWritten: 0,
        _handle: [Zlib],
        _outBuffer:
         <Buffer 4e 6f 74 20 46 6f 75 6e 64 83 cb e7 55 7f 00 00 10 5b 87 02 00 00 00 00 10 5b 87 02 00 00 00 00 d0 58 87 02 00 00 00 00 f8 ed 88 02 00 00 00 00 0b 00 ... >,
        _outOffset: 0,
        _chunkSize: 16384,
        _defaultFlushFlag: 2,
        _finishFlushFlag: 2,
        _nextFlush: -1,
        _defaultFullFlushFlag: 3,
        _info: undefined,
        _level: -1,
        _strategy: 0 },
     disturbed: false,
     error: null },
  [Symbol(Response internals)]:
   { url:
      'https://github.com/Geode-solutions/OpenGeode/releases/download/v2.2.0/OpenGeode-2.2.0-Linux.tar.gz',
     status: 404,
     statusText: 'Not Found',
     headers: Headers { [Symbol(map)]: [Object] },
     counter: 0 } }

The same token is used with octokit and it works to call the github API.

@BotellaA
Copy link
Author

@gr2m Any idea of why it does not work? Are you able to reproduce this issue?

@gr2m
Copy link
Contributor

gr2m commented Jul 13, 2019

I was able to reproduce the problem. I’ve created a test repository and invited you to it:
https://github.com/gr2m/octokit-rest.js-1417/invitations

I have no workaround I’m afraid. This is a limitation with the CORS settings on GitHub’s S3 as far as I can tell. I’ll investigate some more and then bring it up with GitHub’s support/security team. There are a few other use cases where missing CORS settings is a problem for usage in browser: https://github.com/octokit/rest.js/issues?q=is%3Aissue+is%3Aopen+label%3A%22blocked+by+api%22

@gr2m
Copy link
Contributor

gr2m commented Jul 13, 2019

Looks like this does not throw an error

const assetUrl = https://api.github.com/repos/gr2m/octokit-rest.js-1417/releases/assets/13689345
const token = 'your token here'
const response = await fetch(`${assetUrl}?access_token=${token}`, {
  headers: {
    accept: 'application/octet-stream'
  },
  mode: 'no-cors'
})

but response.body is null. My understanding is that no-cors means you cannot get access to the returned data at all

@BotellaA
Copy link
Author

@gr2m any update or news from GitHub’s support/security team?

@gr2m
Copy link
Contributor

gr2m commented Jul 23, 2019

I’ll bring it up tomorrow in a meeting, I’ll keep you posted here.

@BotellaA
Copy link
Author

Any feedback from your meeting?

@gr2m
Copy link
Contributor

gr2m commented Jul 30, 2019

Don't expect this to be resolved anytime soon. I will update this issues as soon as there are any news.

@Pytness
Copy link

Pytness commented Oct 12, 2019

Any news?

@gr2m
Copy link
Contributor

gr2m commented Oct 12, 2019

Unfortunately no. The only thing you can do right now is to inform support@github.com that this is a blocking issue for you to bump the priority on the problem internally.

@BotellaA
Copy link
Author

BotellaA commented Feb 4, 2020

@gr2m any update? Github is deprecating the workarround with access_token: https://developer.github.com/changes/2019-11-05-deprecated-passwords-and-authorizations-api/#authenticating-using-query-parameters

@gr2m
Copy link
Contributor

gr2m commented Feb 4, 2020

No :( I've let them know but didn't hear back. I'll have a call with some folks of the API team tomorrow and will bring it up directly, I'll keep you posted

@latica-jp
Copy link

Hello @gr2m, any update on this?

@gr2m
Copy link
Contributor

gr2m commented Apr 9, 2020

I promise that as soon as there is an update I will post it here. For the time being, the best you can do is to contact support at https://support.github.com/contact and let them know about your use case. They are aware of the problem, but the more people report it and share their use cases, the higher the priority will get.

Thank you all for your patience and support

@edospadoni
Copy link

Hi, have you tried with:

request(
  {
    url:
      "https://api.github.com/repos/:owner/:repo/releases/assets/:asset_id",
    method: "GET",
    headers: {
      Accept: "application/octet-stream",
      Authorization: "token " + process.env.GITHUB_TOKEN,
      "User-Agent": "",
    },
  },
  function (error, response, body) {
    console.log(body);
  }
);

The body should contains the content of your asset.

You can do it also with curl:

curl -v -L -H 'Accept: application/octet-stream' -H 'Authorization: token YOUR_TOKEN' https://api.github.com/repos/:owner/:repo/releases/assets/:asset_id

@ebenoist
Copy link

ebenoist commented May 6, 2020

This won't for private assets that redirect to s3. AWS complains that the Authorization header is present. You can use the query param to work around this, but it looks like that support is being deprecated.

@edospadoni
Copy link

edospadoni commented May 7, 2020

This won't for private assets that redirect to s3. AWS complains that the Authorization header is present. You can use the query param to work around this, but it looks like that support is being deprecated.

I'm using the above code to retrive private assets that redirect to S3 and it works for me.

The -L params in curl do the job.

In the nodeJS part, request follows automatically the redirects (for GET requests) but you have to add:

"User-Agent": ""

because without you get this error:

Request forbidden by administrative rules. Please make sure your request has a User-Agent header (http://developer.github.com/v3/#user-agent-required). Check https://developer.github.com for other possible causes.

Have you try it? Let me know.

@blaadje
Copy link

blaadje commented Jun 4, 2020

Not working for me either with private repos.
I really need that to provide assets to a landing page from a private repo, do you have any update ?

@gr2m
Copy link
Contributor

gr2m commented Jun 4, 2020

No update. But I created a dedicated repository for GitHub API limitations, including the use cases that GitHub users have for them. I hope that will help with the decision making on GitHub's site.

Please add your use cases to https://github.com/gr2m/github-api-wishlist/tree/master/wishlist/cors-for-adjacent-domains

@eugene1g
Copy link

eugene1g commented Jun 24, 2020

Thanks @edospadoni for the hint!

For now, we have to download assets from private repos via plain HTTP calls outside Octokit. I use got for the HTTP call -

// TypeScript implementation:
import got from "got";
import { pipeline } from "stream";
import { createWriteStream } from "fs";
import { promisify } from "util";
const asyncPipeline = promisify(pipeline);

type TDownload = {
  owner: string;
  repo: string;
  assetId: number;
  toLocalFile: string;
  githubToken?: string;
};

export async function downloadGithubAsset(dl: TDownload) {
  const token = dl.githubToken || process.env.GITHUB_TOKEN;
  return asyncPipeline(
    got.stream(
      `https://api.github.com/repos/${dl.owner}/${dl.repo}/releases/assets/${dl.assetId}`,
      {
        method: "GET",
        headers: {
          Accept: "application/octet-stream",
          Authorization: `token ${token}`,
          "User-Agent": "",
        },
      }
    ),
    createWriteStream(dl.toLocalFile)
  );
}

@bebraw
Copy link

bebraw commented Jan 13, 2021

Based on https://developer.github.com/changes/2020-02-10-deprecating-auth-through-query-param/, access_token will be removed 5th of May so it would be great if Octokit could support auth for private release assets by then.

My understanding is that a part of the issue is that fetch is handling redirect transparently within its implementation and then passing the auth headers to the target of the redirect resulting in an error.

I imagine in ideal case the fix would be on server-side so that fetch would work and possibly that would fix Octokit as well as a side effect.

My use case is downloading private assets at a Next.js site and then serving them through it. I'm publishing private fonts this way by using GitHub releases as a registry and Next.js as my proxy.

@bebraw
Copy link

bebraw commented Jan 13, 2021

For what it's worth, here's an Axios based solution that seems to work:

async function getAsset(url) {
  const response = await axios.get(url, {
    headers: {
      Accept: "application/octet-stream",
      Authorization: `token ${process.env.GITHUB_TOKEN}`,
      "User-Agent": ""
    },
    responseType: "arraybuffer"
  });

  return response.data;
}

It expects an url from an asset object gained through GitHub API as a parameter.

@gr2m
Copy link
Contributor

gr2m commented Jan 13, 2021

You can also use

const requestOptions = octokit
  .request.endpoint("GET /repos/:owner/:repo/releases/assets/:asset_id", {
      headers: {
         Accept: "application/octet-stream"
      },
      owner,
      repo,
      asset_id
});

to get generic request options (except the authorization header), see https://github.com/octokit/endpoint.js. You can pass the requestOptions to axios or any custom request library.

@gr2m gr2m transferred this issue from octokit/octokit.js Apr 18, 2021
@gr2m gr2m added Status: Blocked Some technical or requirement is blocking the issue Type: Bug Something isn't working as documented labels Apr 18, 2021
@beyarkay
Copy link

@gr2m no chance there's a ETA or any sort of timeline on this issue? It's causing real pain for my users

@gr2m
Copy link
Contributor

gr2m commented Jan 9, 2023

Sorry, I'm not aware of any movement on this, unfortunately. Maybe @nickfloyd can check, but as this has been open for so long, I wouldn't get my hopes up.

@mathieucarbou
Copy link

any solution ?

Access to XMLHttpRequest at ... (redirected from ...) from origin 'http://localhost:5173' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header has a value 'https://github.com' that is not equal to the supplied origin.

@Stuyk
Copy link

Stuyk commented Oct 26, 2023

Here's a working solution, best of luck all. 👍

const { Octokit } = require("octokit");
const { Readable } = require('stream');
const fs = require('fs'); // For file operations

const github_pat = 'some-pat-token';

async function downloadAsset(id, repo, fileName) {
    const opts = octokit.request.endpoint('GET /repos/{owner}/{repo}/releases', {
        headers: {
            Accept: "application/octet-stream",
            Authorization: `token ${github_pat}`
        },
        owner: 'some-owner',
        repo
    })

    const response = await fetch(`https://api.github.com/repos/some-owner/${repo}/releases/assets/${id}`, { headers: opts.headers });

    if (!fs.existsSync('downloads')) {
        fs.mkdirSync('downloads');
    }

    if (!response || !response.status === 200) {
        return { status: false, message: `Failed to download asset ${fileName}` };
    }

    const body = Readable.fromWeb(response.body);
    const download_write_stream = fs.createWriteStream(`downloads/${fileName}`);
    body.pipe(download_write_stream);

    return new Promise((resolve) => {
        download_write_stream.on('finish', () => {
            return resolve({ status: true, message: `Downloaded - ${fileName}` });
        });

        download_write_stream.on('error', (err) => {
            console.log(err);
            return resolve({ status: false, message: `Failed to Download - ${fileName}` });
        });
    })
}

@amacneil
Copy link

amacneil commented Jan 30, 2024

Here is the magic required to stream release assets to a file using rest.js, typescript, and async/await:

import { Octokit } from "@octokit/rest";
import { createWriteStream } from "node:fs";
import { join } from "node:path";
import { pipeline } from "node:stream/promises";

async function fetchAsset() {
  const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
  const asset = await octokit.rest.repos.getReleaseAsset({
    owner: "foxglove",
    repo: "app",
    asset_id: 12345,
    headers: {
      accept: "application/octet-stream",
    },
    request: {
      parseSuccessResponseBody: false, // required to access response as stream
    },
  });

  const assetStream = asset.data as unknown as NodeJS.ReadableStream;
  const outputFile = createWriteStream(join("outputdir", "file.zip"));
  await pipeline(assetStream, outputFile);
}

@wolfy1339
Copy link
Member

I think we completely forgot about the return type to account for the Stream usage

@amacneil
Copy link

Yeah, the return type is a problem even without the streaming option too. TypeScript thinks it’s a normal shaped response object, but instead it returns an ArrayBuffer iirc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Status: Blocked Some technical or requirement is blocking the issue Type: Bug Something isn't working as documented
Projects
None yet
Development

No branches or pull requests