github-app-installation-token
Version:
npm/script and binary 📦 to get a token from a GitHub App
121 lines (100 loc) • 3.69 kB
text/typescript
import ora from 'ora';
import { Octokit } from '@octokit/rest';
import type { RequestRequestOptions } from '@octokit/types';
import { createAppAuth } from '@octokit/auth-app';
import NodeRSA from 'node-rsa';
import { SUCCESS_SYMBOL } from '../utils/constants';
import { logger } from '../utils/debug';
import {
AppsCreateInstallationAccessTokenResponse,
isAppsCreateInstallationAccessTokenResponse,
isError,
} from '../utils/guards';
import { readContent } from '../utils/readFile';
import type { Command } from 'commander';
const debug = logger('generate');
// just left the async signature to make it easier in the future
interface GetTokenInput {
appId: number;
installationId: number;
privateKey: string;
baseUrl?: string;
}
type RequestOptions = RequestRequestOptions & Required<{ rawResponse: true }>;
type PlainRequest = RequestRequestOptions & { rawResponse?: false };
export async function getToken(
{ appId, installationId, privateKey }: GetTokenInput,
requestOptions: RequestOptions
): Promise<AppsCreateInstallationAccessTokenResponse>;
export async function getToken(
{ appId, installationId, privateKey }: GetTokenInput,
requestOptions?: PlainRequest
): Promise<Pick<AppsCreateInstallationAccessTokenResponse, 'token'>>;
export async function getToken(
{ appId, installationId, privateKey, baseUrl }: GetTokenInput,
requestOptions?: PlainRequest | RequestOptions
): Promise<Pick<AppsCreateInstallationAccessTokenResponse, 'token'> | AppsCreateInstallationAccessTokenResponse> {
const key = new NodeRSA(privateKey);
let request: Request;
if (requestOptions?.rawResponse) {
const { rawResponse: _rawResponse, ...rest } = requestOptions;
request = rest as Request;
} else {
request = requestOptions as Request;
}
const octokit = new Octokit({
authStrategy: createAppAuth,
auth: {
appId,
id: appId,
privateKey: key.exportKey('pkcs8-private-pem'),
},
baseUrl,
request,
});
const response = await octokit.auth({
type: 'installation',
installationId,
});
if (!isAppsCreateInstallationAccessTokenResponse(response)) {
debug(`response is missing the token, we got ${response}`);
throw new Error('Something went wrong on the token retrieval, enable debug to inspect further');
}
return requestOptions?.rawResponse ? response : { token: response.token };
}
type Input = Omit<Command & GetTokenInput, 'privateKey'> & {
privateKeyLocation?: string;
privateKey?: string;
rawResponse?: boolean;
};
const isValidInput = (input: Input): boolean => {
return !!(input.privateKey || input.privateKeyLocation);
};
export const command = async (input: Input): Promise<void> => {
debug('input:', input);
const loader = ora('Retrieving token...').start();
try {
let privateKey: string;
const { privateKeyLocation, installationId, appId, rawResponse } = input;
if (!isValidInput(input)) {
loader.fail('Input is not valid, either privateKey or privateKeyLocation should be provided');
process.exit(1);
}
if (!input.privateKey && privateKeyLocation) {
privateKey = await readContent(privateKeyLocation);
} else {
privateKey = input.privateKey as string;
}
const response = await getToken({ privateKey, installationId, appId }, { rawResponse: true });
loader.stopAndPersist({
text: `The token is: ${response.token} and expires ${response.expiresAt}`,
symbol: SUCCESS_SYMBOL,
});
console.log(rawResponse ? response : response.token);
} catch (e: unknown) {
if (isError(e)) {
loader.fail(`We encountered an error: ${e.message}`);
}
process.exit(1);
}
};