octokit/app.js

[Support] How to use `app.octokit.request()`?

SwanX1 opened this issue · 5 comments

I'm having an issue when requesting an endpoint using app.octokit.request(), how do I fix this?

Example code (typescript):

import { config } from 'dotenv';
import { App } from '@octokit/app';
import { readFileSync } from 'fs';

config();

const app = new App({
  appId: process.env.APP_ID ?? '',
  privateKey: readFileSync('./private_key.pem').toString(),
});

app.octokit.request('GET /repos/{owner}/{repo}/commits', {
  owner: 'microsoft',
  repo: 'vscode',
}).catch(err => {
  if (err instanceof Error) {
    console.error(err.stack);
  }
});

Output:

Error: [@octokit/auth-app] installationId option is required for installation authentication.
    at getInstallationAuthentication (node_modules/@octokit/auth-app/dist-node/index.js:213:11)
    at hook (node_modules/@octokit/auth-app/dist-node/index.js:437:13)
    at node_modules/before-after-hook/lib/register.js:25:15

This also doesn't work:

const app = new App({
  appId: process.env.APP_ID ?? '',
  privateKey: readFileSync('./private_key.pem').toString(),
  Octokit: Octokit.defaults({
    authStrategy: createAppAuth,
    auth: {
      appId: process.env.APP_ID,
      privateKey: readFileSync('./private_key.pem').toString(),
      clientId: process.env.CLIENT_ID,
      clientSecret: process.env.CLIENT_SECRET,
      installationId: 123,
    },
  }),
});
gr2m commented

All requests sent using app.octokit.request() are authenticated as the application. All endpoints listed at https://docs.github.com/en/rest/reference/apps which say

You must use a JWT to access this endpoint

will work.

GET /repos/{owner}/{repo}/commits requires authentication as an installation. An app can only access that endpoint if it is installed on the {owner}/{repo} repository. And if it is, you need to send the the request with installation authentication.

I considered to automate the authentication of app.octokit.request() based on the request URL, but I didn't get to it yet, and it wouldn't work for all REST API routes because GitHub does not yet consistently implement the namespacing for all routes requiring authentication using an installation access token.

In your case, try this:

const { data: installation } = app.octokit.request('GET /repos/{owner}/{repo}/installation', {
  owner: 'microsoft',
  repo: 'vscode',
})
const installationOctokit = await app.getInstallationOctokit(installation.id)
installationOctokit.request('GET /repos/{owner}/{repo}/commits', {
  owner: 'microsoft',
  repo: 'vscode',
})

By the way for the GET /repos/{owner}/{repo}/commits endpoint only returns the first 30 commits by default. If you want to retrieve all commits I'd recommend https://github.com/octokit/plugin-paginate-rest.js. If you use the App export from the octokit package you'll get the pagination plugin out of the box.

gr2m commented

I'll close the issue because there is nothing actionable, but please feel free to continue to ask follow up questions

I've run into a similar issue here and I'm trying to use your workaround to no avail:

const app = new App({
  appId: <...>,
  privateKey: <...>,
  oauth: {
    clientId: <...>,
    clientSecret: <...>,
  },
  webhooks: { secret: <...> },
});


app.webhooks.on('installation_repositories', async ({ id, payload }) => {
  const octokit = await app.getInstallationOctokit(payload.installation.id);
  const { data } = await octokit.request('GET /installations/repositories'); // This request fails with 401
  console.log('--------installation event--------');
  console.log(id, payload, data);
});

Any idea what I'm doing wrong?

Nevermind, figured it out - it was an issue with the private key not being in the right format.

const app = new App({
  ...,
  privateKey: process.env.GITHUB_PRIVATE_KEY.replace(/\\n/gm, '\n'), // This replace fixed it!
  ...
});