Refactor GitHubAppManager, add deps & config

This commit is contained in:
Johan Haals
2021-01-11 14:28:56 +01:00
parent 5d20d4bad8
commit 25a5d6bf2f
5 changed files with 109 additions and 117 deletions
+10
View File
@@ -35,6 +35,16 @@ export interface Config {
apiBaseUrl?: string;
/** @visibility frontend */
rawBaseUrl?: string;
apps?: Array<{
appId: number;
/** @visiblity secret */
privateKey: string;
/** @visiblity secret */
webhookSecret: string;
clientId: string;
/** @visiblity secret */
clientSecret: string;
}>;
}>;
gitlab?: Array<{
+4 -1
View File
@@ -31,7 +31,10 @@
"dependencies": {
"@backstage/config": "^0.1.2",
"cross-fetch": "^3.0.6",
"git-url-parse": "^11.4.3"
"git-url-parse": "^11.4.3",
"@octokit/rest": "^18.0.12",
"@octokit/auth-app": "^2.10.5",
"moment": "^2.29.1"
},
"devDependencies": {
"@backstage/cli": "^0.4.5",
+14
View File
@@ -58,8 +58,22 @@ export type GitHubIntegrationConfig = {
* If no token is specified, anonymous access is used.
*/
token?: string;
/**
* The GitHub Apps configuration to use for requests to this provider.
*
* If no apps is specified, token or anonymous is used.
*/
apps?: GithubAppConfig[];
};
export type GithubAppConfig = {
appId: number;
privateKey: string;
webhookSecret: string;
clientId: string;
clientSecret: string;
};
/**
* Reads a single GitHub integration config.
*
+47 -30
View File
@@ -19,6 +19,7 @@ import { createAppAuth } from '@octokit/auth-app';
import { Octokit, RestEndpointMethodTypes } from '@octokit/rest';
import gitUrlParse from 'git-url-parse';
import moment from 'moment';
import { InstallationAccessTokenAuthentication } from '@octokit/auth-app/dist-types/types';
type InstallationData = {
installationId: number;
@@ -26,15 +27,37 @@ type InstallationData = {
repositorySelection: 'selected' | 'all';
};
class Cache {
private readonly tokenCache = new Map<
string,
{ token: string; expiresAt: Date }
>();
async getToken(
key: string,
fn: () => Promise<{ token: string; expiresAt: Date }>,
): Promise<{ accessToken: string }> {
const item = this.tokenCache.get(key);
if (item && this.isNotExpired(item.expiresAt)) {
return { accessToken: item.token };
}
const result = await fn();
this.tokenCache.set(key, result);
return { accessToken: result.token };
}
// consider timestamps older than 50 minutes to be expired.
private isNotExpired = (date: Date) =>
moment(date).isAfter(moment().subtract(50, 'minutes'));
}
// GithubAppManager issues tokens for a speicifc GitHub App
class GithubAppManager {
private readonly appClient: Octokit;
private readonly baseAuthConfig: { appId: number; privateKey: string };
private installations?: RestEndpointMethodTypes['apps']['listInstallations']['response'];
private readonly tokenCache = new Map<
string,
{ accessToken: string; exp: Date }
>();
private readonly cache = new Cache();
constructor(config: GithubAppConfig) {
this.baseAuthConfig = {
@@ -47,9 +70,6 @@ class GithubAppManager {
});
}
private lessThanOneHourAgo = (date: Date) =>
moment(date).isAfter(moment().subtract(1, 'hours'));
async getInstallationCredentials(
owner: string,
repo: string,
@@ -65,34 +85,31 @@ class GithubAppManager {
// App is installed in the entire org
if (repositorySelection === 'all') {
const auth = createAppAuth({
...this.baseAuthConfig,
installationId,
return this.cache.getToken(owner, async () => {
const auth = createAppAuth({
...this.baseAuthConfig,
installationId,
});
const result = await auth({ type: 'installation' });
const {
token,
expiresAt,
} = result as InstallationAccessTokenAuthentication;
return { token, expiresAt: new Date(expiresAt) };
});
const { token } = await auth({ type: 'installation' });
return { accessToken: token };
}
// App is not installed org wide which requires a specific app token.
const cacheKey = `${owner}/${repo}`;
if (this.tokenCache.has(cacheKey)) {
const item = this.tokenCache.get(cacheKey);
if (this.lessThanOneHourAgo(item?.exp!)) {
return {
accessToken: item?.accessToken!,
};
}
}
const res = await this.appClient.apps.createInstallationAccessToken({
installation_id: installationId,
repositories: [repo],
return this.cache.getToken(`${owner}/${repo}`, async () => {
const result = await this.appClient.apps.createInstallationAccessToken({
installation_id: installationId,
repositories: [repo],
});
return {
token: result.data.token,
expiresAt: new Date(result.data.expires_at),
};
});
this.tokenCache.set(cacheKey, {
accessToken: res.data.token,
exp: new Date(res.data.expires_at),
});
return { accessToken: res.data.token };
}
private async getInstallationData(owner: string): Promise<InstallationData> {
+34 -86
View File
@@ -4661,32 +4661,27 @@
dependencies:
mkdirp "^1.0.4"
"@octokit/auth-token@^2.4.0":
version "2.4.0"
resolved "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.0.tgz#b64178975218b99e4dfe948253f0673cbbb59d9f"
integrity sha512-eoOVMjILna7FVQf96iWc3+ZtE/ZT6y8ob8ZzcqKY1ibSQCnu4O/B7pJvzMx5cyZ/RjAff6DAdEb0O0Cjcxidkg==
"@octokit/auth-app@^2.10.5":
version "2.10.5"
resolved "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-2.10.5.tgz#85d69cb96818f5da34bf0b81bb637d3675ad4e9a"
integrity sha512-6yXyjtcBWpuPYSdZN8z8IIjGSqkPmiJzdmCdod8at41ANB1FtaKbUIDL5+IkG+svv68NIYs+XORbhBRFXYB3bw==
dependencies:
"@octokit/types" "^2.0.0"
"@octokit/request" "^5.4.11"
"@octokit/request-error" "^2.0.0"
"@octokit/types" "^6.0.3"
"@types/lru-cache" "^5.1.0"
deprecation "^2.3.1"
lru-cache "^6.0.0"
universal-github-app-jwt "^1.0.1"
universal-user-agent "^6.0.0"
"@octokit/auth-token@^2.4.4":
"@octokit/auth-token@^2.4.0", "@octokit/auth-token@^2.4.4":
version "2.4.4"
resolved "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.4.tgz#ee31c69b01d0378c12fd3ffe406030f3d94d3b56"
integrity sha512-LNfGu3Ro9uFAYh10MUZVaT7X2CnNm2C8IDQmabx+3DygYIQjs9FwzFAHN/0t6mu5HEPhxcb1XOuxdpY82vCg2Q==
dependencies:
"@octokit/types" "^6.0.0"
"@octokit/core@^3.0.0":
version "3.1.0"
resolved "https://registry.npmjs.org/@octokit/core/-/core-3.1.0.tgz#9c3c9b23f7504668cfa057f143ccbf0c645a0ac9"
integrity sha512-yPyQSmxIXLieEIRikk2w8AEtWkFdfG/LXcw1KvEtK3iP0ENZLW/WYQmdzOKqfSaLhooz4CJ9D+WY79C8ZliACw==
dependencies:
"@octokit/auth-token" "^2.4.0"
"@octokit/graphql" "^4.3.1"
"@octokit/request" "^5.4.0"
"@octokit/types" "^5.0.0"
before-after-hook "^2.1.0"
universal-user-agent "^5.0.0"
"@octokit/core@^3.2.3":
version "3.2.4"
resolved "https://registry.npmjs.org/@octokit/core/-/core-3.2.4.tgz#5791256057a962eca972e31818f02454897fd106"
@@ -4708,15 +4703,6 @@
is-plain-object "^3.0.0"
universal-user-agent "^5.0.0"
"@octokit/graphql@^4.3.1":
version "4.5.1"
resolved "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.5.1.tgz#162aed1490320b88ce34775b3f6b8de945529fa9"
integrity sha512-qgMsROG9K2KxDs12CO3bySJaYoUu2aic90qpFrv7A8sEBzZ7UFGvdgPKiLw5gOPYEYbS0Xf8Tvf84tJutHPulQ==
dependencies:
"@octokit/request" "^5.3.0"
"@octokit/types" "^5.0.0"
universal-user-agent "^5.0.0"
"@octokit/graphql@^4.5.8":
version "4.5.8"
resolved "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.5.8.tgz#d42373633c3015d0eafce64a8ce196be167fdd9b"
@@ -4743,13 +4729,6 @@
dependencies:
"@octokit/types" "^2.0.1"
"@octokit/plugin-paginate-rest@^2.2.0":
version "2.2.3"
resolved "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.2.3.tgz#a6ad4377e7e7832fb4bdd9d421e600cb7640ac27"
integrity sha512-eKTs91wXnJH8Yicwa30jz6DF50kAh7vkcqCQ9D7/tvBAP5KKkg6I2nNof8Mp/65G0Arjsb4QcOJcIEQY+rK1Rg==
dependencies:
"@octokit/types" "^5.0.0"
"@octokit/plugin-paginate-rest@^2.6.2":
version "2.7.0"
resolved "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.7.0.tgz#6bb7b043c246e0654119a6ec4e72a172c9e2c7f3"
@@ -4757,12 +4736,7 @@
dependencies:
"@octokit/types" "^6.0.1"
"@octokit/plugin-request-log@^1.0.0":
version "1.0.0"
resolved "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.0.tgz#eef87a431300f6148c39a7f75f8cfeb218b2547e"
integrity sha512-ywoxP68aOT3zHCLgWZgwUJatiENeHE7xJzYjfz8WI0goynp96wETBF+d95b8g/uL4QmS6owPVlaxiz3wyMAzcw==
"@octokit/plugin-request-log@^1.0.2":
"@octokit/plugin-request-log@^1.0.0", "@octokit/plugin-request-log@^1.0.2":
version "1.0.2"
resolved "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.2.tgz#394d59ec734cd2f122431fbaf05099861ece3c44"
integrity sha512-oTJSNAmBqyDR41uSMunLQKMX0jmEXbwD1fpz8FG27lScV3RhtGfBa1/BBLym+PxcC16IBlF7KH9vP1BUYxA+Eg==
@@ -4775,14 +4749,6 @@
"@octokit/types" "^2.0.1"
deprecation "^2.3.1"
"@octokit/plugin-rest-endpoint-methods@4.1.4":
version "4.1.4"
resolved "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-4.1.4.tgz#ca60736d761b304fec02a2608caaec2d822e9835"
integrity sha512-Y2tVpSa7HjV3DGIQrQOJcReJ2JtcN9FaGr9jDa332Flro923/h3/Iu9e7Y4GilnzfLclHEh5iCQoCkHm7tWOcg==
dependencies:
"@octokit/types" "^5.4.1"
deprecation "^2.3.1"
"@octokit/plugin-rest-endpoint-methods@4.4.1":
version "4.4.1"
resolved "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-4.4.1.tgz#105cf93255432155de078c9efc33bd4e14d1cd63"
@@ -4809,21 +4775,7 @@
deprecation "^2.0.0"
once "^1.4.0"
"@octokit/request@^5.2.0", "@octokit/request@^5.3.0", "@octokit/request@^5.4.0":
version "5.4.5"
resolved "https://registry.npmjs.org/@octokit/request/-/request-5.4.5.tgz#8df65bd812047521f7e9db6ff118c06ba84ac10b"
integrity sha512-atAs5GAGbZedvJXXdjtKljin+e2SltEs48B3naJjqWupYl2IUBbB/CJisyjbNHcKpHzb3E+OYEZ46G8eakXgQg==
dependencies:
"@octokit/endpoint" "^6.0.1"
"@octokit/request-error" "^2.0.0"
"@octokit/types" "^5.0.0"
deprecation "^2.0.0"
is-plain-object "^3.0.0"
node-fetch "^2.3.0"
once "^1.4.0"
universal-user-agent "^5.0.0"
"@octokit/request@^5.4.12":
"@octokit/request@^5.2.0", "@octokit/request@^5.3.0", "@octokit/request@^5.4.11", "@octokit/request@^5.4.12":
version "5.4.12"
resolved "https://registry.npmjs.org/@octokit/request/-/request-5.4.12.tgz#b04826fa934670c56b135a81447be2c1723a2ffc"
integrity sha512-MvWYdxengUWTGFpfpefBBpVmmEYfkwMoxonIB3sUGp5rhdgwjXL1ejo6JbgzG/QD9B/NYt/9cJX1pxXeSIUCkg==
@@ -4859,17 +4811,7 @@
once "^1.4.0"
universal-user-agent "^4.0.0"
"@octokit/rest@^18.0.0":
version "18.0.5"
resolved "https://registry.npmjs.org/@octokit/rest/-/rest-18.0.5.tgz#1f1498dcdc2d85d0f86b8168e4ff5779842b2742"
integrity sha512-SPKI24tQXrr1XsnaIjv2x0rl4M5eF1+hj8+vMe3d/exZ7NnL5sTe1BuFyCyJyrc+j1HkXankvgGN9zT0rwBwtg==
dependencies:
"@octokit/core" "^3.0.0"
"@octokit/plugin-paginate-rest" "^2.2.0"
"@octokit/plugin-request-log" "^1.0.0"
"@octokit/plugin-rest-endpoint-methods" "4.1.4"
"@octokit/rest@^18.0.12":
"@octokit/rest@^18.0.0", "@octokit/rest@^18.0.12":
version "18.0.12"
resolved "https://registry.npmjs.org/@octokit/rest/-/rest-18.0.12.tgz#278bd41358c56d87c201e787e8adc0cac132503a"
integrity sha512-hNRCZfKPpeaIjOVuNJzkEL6zacfZlBPV8vw8ReNeyUkVvbuCvvrrx8K8Gw2eyHHsmd4dPlAxIXIZ9oHhJfkJpw==
@@ -4887,16 +4829,9 @@
"@types/node" ">= 8"
"@octokit/types@^5.0.0", "@octokit/types@^5.0.1":
version "5.0.1"
resolved "https://registry.npmjs.org/@octokit/types/-/types-5.0.1.tgz#5459e9a5e9df8565dcc62c17a34491904d71971e"
integrity sha512-GorvORVwp244fGKEt3cgt/P+M0MGy4xEDbckw+K5ojEezxyMDgCaYPKVct+/eWQfZXOT7uq0xRpmrl/+hliabA==
dependencies:
"@types/node" ">= 8"
"@octokit/types@^5.4.1":
version "5.4.1"
resolved "https://registry.npmjs.org/@octokit/types/-/types-5.4.1.tgz#d5d5f2b70ffc0e3f89467c3db749fa87fc3b7031"
integrity sha512-OlMlSySBJoJ6uozkr/i03nO5dlYQyE05vmQNZhAh9MyO4DPBP88QlwsDVLmVjIMFssvIZB6WO0ctIGMRG+xsJQ==
version "5.5.0"
resolved "https://registry.npmjs.org/@octokit/types/-/types-5.5.0.tgz#e5f06e8db21246ca102aa28444cdb13ae17a139b"
integrity sha512-UZ1pErDue6bZNjYOotCNveTXArOMZQFG6hKJfOnGnulVCMcVVi7YIIuuR4WfBhjo7zgpmzn/BkPDnUXtNx+PcQ==
dependencies:
"@types/node" ">= 8"
@@ -6631,7 +6566,7 @@
resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
"@types/jsonwebtoken@^8.5.0":
"@types/jsonwebtoken@^8.3.3", "@types/jsonwebtoken@^8.5.0":
version "8.5.0"
resolved "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.0.tgz#2531d5e300803aa63279b232c014acf780c981c5"
integrity sha512-9bVao7LvyorRGZCw0VmH/dr7Og+NdjYSsKAxB43OQoComFbBgsEpoR9JW6+qSq/ogwVBg8GI2MfAlk4SYI4OLg==
@@ -6694,6 +6629,11 @@
resolved "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9"
integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==
"@types/lru-cache@^5.1.0":
version "5.1.0"
resolved "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.0.tgz#57f228f2b80c046b4a1bd5cac031f81f207f4f03"
integrity sha512-RaE0B+14ToE4l6UqdarKPnXwVDuigfFv+5j9Dze/Nqr23yyuqdNvzcZi3xB+3Agvi5R4EOgAksfv3lXX4vBt9w==
"@types/markdown-to-jsx@^6.11.0":
version "6.11.2"
resolved "https://registry.npmjs.org/@types/markdown-to-jsx/-/markdown-to-jsx-6.11.2.tgz#05d1aaffbf15be7be12c70535fa4fed65cc7c64f"
@@ -18484,7 +18424,7 @@ modify-values@^1.0.0:
resolved "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022"
integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==
moment@^2.25.3, moment@^2.26.0, moment@^2.27.0:
moment@^2.25.3, moment@^2.26.0, moment@^2.27.0, moment@^2.29.1:
version "2.29.1"
resolved "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
@@ -25155,6 +25095,14 @@ unist-util-visit@^2.0.0:
unist-util-is "^4.0.0"
unist-util-visit-parents "^3.0.0"
universal-github-app-jwt@^1.0.1:
version "1.1.0"
resolved "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-1.1.0.tgz#0abaa876101cdf1d3e4c546be2768841c0c1b514"
integrity sha512-3b+ocAjjz4JTyqaOT+NNBd5BtTuvJTxWElIoeHSVelUV9J3Jp7avmQTdLKCaoqi/5Ox2o/q+VK19TJ233rVXVQ==
dependencies:
"@types/jsonwebtoken" "^8.3.3"
jsonwebtoken "^8.5.1"
universal-user-agent@^4.0.0:
version "4.0.1"
resolved "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-4.0.1.tgz#fd8d6cb773a679a709e967ef8288a31fcc03e557"