Skip to content

Commit 816842b

Browse files
NONE fix conflicts token installatin id (#2019)
1 parent 5d9ea63 commit 816842b

File tree

4 files changed

+105
-9
lines changed

4 files changed

+105
-9
lines changed

src/github/client/github-installation-client.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,7 @@ export class GitHubInstallationClient extends GitHubClient {
340340
private async installationAuthenticationHeaders(): Promise<Partial<AxiosRequestConfig>> {
341341
const installationToken = await this.installationTokenCache.getInstallationToken(
342342
this.githubInstallationId.installationId,
343+
this.gitHubServerAppId,
343344
() => this.createInstallationToken(this.githubInstallationId.installationId));
344345
return {
345346
headers: {

src/github/client/installation-token-cache.test.ts

Lines changed: 93 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,96 @@ describe("InstallationTokenCache", () => {
1818
jest.useRealTimers();
1919
});
2020

21+
it("Reuse same token for cloud installation", async () => {
22+
23+
const GITHUB_INSTALLATION_ID = 1;
24+
jest.setSystemTime(now);
25+
const token1 = new AuthToken("token1", in10Minutes);
26+
const token2 = new AuthToken("token2", in10Minutes);
27+
28+
const cache1 = InstallationTokenCache.getInstance();
29+
const cache2 = InstallationTokenCache.getInstance();
30+
31+
const foundToken1 = await cache1.getInstallationToken(GITHUB_INSTALLATION_ID, undefined, () => Promise.resolve(token1));
32+
const foundToken2 = await cache2.getInstallationToken(GITHUB_INSTALLATION_ID, undefined, () => Promise.resolve(token2));
33+
34+
expect(foundToken1).toEqual(foundToken2);
35+
36+
});
37+
38+
it("Reuse same token for GHE installation", async () => {
39+
40+
const GITHUB_INSTALLATION_ID = 1;
41+
const GITHUB_APP_ID = 1;
42+
jest.setSystemTime(now);
43+
const token1 = new AuthToken("token1", in10Minutes);
44+
const token2 = new AuthToken("token2", in10Minutes);
45+
46+
const cache1 = InstallationTokenCache.getInstance();
47+
const cache2 = InstallationTokenCache.getInstance();
48+
49+
const foundToken1 = await cache1.getInstallationToken(GITHUB_INSTALLATION_ID, GITHUB_APP_ID, () => Promise.resolve(token1));
50+
const foundToken2 = await cache2.getInstallationToken(GITHUB_INSTALLATION_ID, GITHUB_APP_ID, () => Promise.resolve(token2));
51+
52+
expect(foundToken1).toEqual(foundToken2);
53+
54+
});
55+
56+
it("won't have conflicts on the token for cloud installations", async () => {
57+
58+
const GITHUB_INSTALLATION_ID_1 = 21;
59+
const GITHUB_INSTALLATION_ID_2 = 22;
60+
jest.setSystemTime(now);
61+
const token1 = new AuthToken("token1", in10Minutes);
62+
const token2 = new AuthToken("token2", in10Minutes);
63+
64+
const cache1 = InstallationTokenCache.getInstance();
65+
const cache2 = InstallationTokenCache.getInstance();
66+
67+
const foundToken1 = await cache1.getInstallationToken(GITHUB_INSTALLATION_ID_1, undefined, () => Promise.resolve(token1));
68+
const foundToken2 = await cache2.getInstallationToken(GITHUB_INSTALLATION_ID_2, undefined, () => Promise.resolve(token2));
69+
70+
expect(foundToken1).not.toEqual(foundToken2);
71+
72+
});
73+
74+
it("won't have conflicts on the token for different GHE installations", async () => {
75+
76+
const CONFLICTIN_GITHUB_INSTALLATION_ID = 31;
77+
const GITHUB_APP_ID_1 = 31;
78+
const GITHUB_APP_ID_2 = 32;
79+
jest.setSystemTime(now);
80+
const token1 = new AuthToken("token1", in10Minutes);
81+
const token2 = new AuthToken("token2", in10Minutes);
82+
83+
const cache1 = InstallationTokenCache.getInstance();
84+
const cache2 = InstallationTokenCache.getInstance();
85+
86+
const foundToken1 = await cache1.getInstallationToken(CONFLICTIN_GITHUB_INSTALLATION_ID, GITHUB_APP_ID_1, () => Promise.resolve(token1));
87+
const foundToken2 = await cache2.getInstallationToken(CONFLICTIN_GITHUB_INSTALLATION_ID, GITHUB_APP_ID_2, () => Promise.resolve(token2));
88+
89+
expect(foundToken1).not.toEqual(foundToken2);
90+
91+
});
92+
93+
it("won't have conflicts on the token for different cloud and GHE installations", async () => {
94+
95+
const CONFLICTIN_GITHUB_INSTALLATION_ID = 41;
96+
const GITHUB_APP_ID = 41;
97+
jest.setSystemTime(now);
98+
const token1 = new AuthToken("token1", in10Minutes);
99+
const token2 = new AuthToken("token2", in10Minutes);
100+
101+
const cache1 = InstallationTokenCache.getInstance();
102+
const cache2 = InstallationTokenCache.getInstance();
103+
104+
const foundToken1 = await cache1.getInstallationToken(CONFLICTIN_GITHUB_INSTALLATION_ID, undefined, () => Promise.resolve(token1));
105+
const foundToken2 = await cache2.getInstallationToken(CONFLICTIN_GITHUB_INSTALLATION_ID, GITHUB_APP_ID, () => Promise.resolve(token2));
106+
107+
expect(foundToken1).not.toEqual(foundToken2);
108+
109+
});
110+
21111
it("Re-generates expired tokens", async () => {
22112
const initialInstallationToken = new AuthToken("initial installation token", in10Minutes);
23113
const generateInitialInstallationToken = jest.fn().mockImplementation(() => Promise.resolve(initialInstallationToken));
@@ -29,21 +119,21 @@ describe("InstallationTokenCache", () => {
29119
const installationTokenCache = new InstallationTokenCache();
30120

31121
jest.setSystemTime(now);
32-
const token1 = await installationTokenCache.getInstallationToken(githubInstallationId, generateInitialInstallationToken);
122+
const token1 = await installationTokenCache.getInstallationToken(githubInstallationId, undefined, generateInitialInstallationToken);
33123
expect(token1).toEqual(initialInstallationToken);
34124
expect(generateInitialInstallationToken).toHaveBeenCalledTimes(1);
35125
expect(generateFreshInstallationToken).toHaveBeenCalledTimes(0);
36126

37127
// after 5 minutes we still expect the same token because it's still valid
38128
jest.setSystemTime(in5Minutes);
39-
const token2 = await installationTokenCache.getInstallationToken(githubInstallationId, generateFreshInstallationToken);
129+
const token2 = await installationTokenCache.getInstallationToken(githubInstallationId, undefined, generateFreshInstallationToken);
40130
expect(token2).toEqual(initialInstallationToken);
41131
expect(generateInitialInstallationToken).toHaveBeenCalledTimes(1);
42132
expect(generateFreshInstallationToken).toHaveBeenCalledTimes(0);
43133

44134
// after 10 minutes we expect a new token because the old one has expired
45135
jest.setSystemTime(in10Minutes);
46-
const token3 = await installationTokenCache.getInstallationToken(githubInstallationId, generateFreshInstallationToken);
136+
const token3 = await installationTokenCache.getInstallationToken(githubInstallationId, undefined, generateFreshInstallationToken);
47137
expect(token3).toEqual(freshInstallationToken);
48138
expect(generateInitialInstallationToken).toHaveBeenCalledTimes(1);
49139
expect(generateFreshInstallationToken).toHaveBeenCalledTimes(1);

src/github/client/installation-token-cache.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { AuthToken } from "./auth-token";
1010
*/
1111
export class InstallationTokenCache {
1212
private static instance: InstallationTokenCache;
13-
private readonly installationTokenCache: LRUCache<number, AuthToken>;
13+
private readonly installationTokenCache: LRUCache<string, AuthToken>;
1414

1515
/**
1616
* Creates a new InstallationTokenCache. This cache should be shared between all GitHub clients so that the clients don't
@@ -19,7 +19,7 @@ export class InstallationTokenCache {
1919
* number, the least recently used tokens are evicted from the cache.
2020
*/
2121
constructor() {
22-
this.installationTokenCache = new LRUCache<number, AuthToken>({ max: 1000 });
22+
this.installationTokenCache = new LRUCache<string, AuthToken>({ max: 1000 });
2323
}
2424

2525
public static getInstance(): InstallationTokenCache {
@@ -39,12 +39,13 @@ export class InstallationTokenCache {
3939
*/
4040
public async getInstallationToken(
4141
githubInstallationId: number,
42+
gitHubAppId: number | undefined,
4243
generateNewInstallationToken: () => Promise<AuthToken>): Promise<AuthToken> {
43-
let token = this.installationTokenCache.get(githubInstallationId);
44+
let token = this.installationTokenCache.get(this.key(githubInstallationId, gitHubAppId));
4445

4546
if (!token || token.isAboutToExpire()) {
4647
token = await generateNewInstallationToken();
47-
this.installationTokenCache.set(githubInstallationId, token, token.millisUntilAboutToExpire());
48+
this.installationTokenCache.set(this.key(githubInstallationId, gitHubAppId), token, token.millisUntilAboutToExpire());
4849
}
4950

5051
return token;
@@ -53,4 +54,8 @@ export class InstallationTokenCache {
5354
public clear(): void {
5455
this.installationTokenCache.reset();
5556
}
57+
58+
private key(githubInstallationId: number, gitHubAppId: number | undefined): string {
59+
return `${githubInstallationId}_${gitHubAppId}`;
60+
}
5661
}

src/github/client/token-cache.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ describe("InstallationTokenCache & AppTokenHolder", () => {
3131
const generateInitialInstallationToken = jest.fn().mockImplementation(() => Promise.resolve(initialInstallationToken));
3232

3333
jest.setSystemTime(date);
34-
const token1 = await installationTokenCache.getInstallationToken(githubInstallationId, generateInitialInstallationToken);
35-
const token2 = await installationTokenCache.getInstallationToken(githubInstallationId, generateInitialInstallationToken);
34+
const token1 = await installationTokenCache.getInstallationToken(githubInstallationId, undefined, generateInitialInstallationToken);
35+
const token2 = await installationTokenCache.getInstallationToken(githubInstallationId, undefined, generateInitialInstallationToken);
3636
expect(token1).toEqual(initialInstallationToken);
3737
expect(token2).toEqual(initialInstallationToken);
3838
expect(generateInitialInstallationToken).toHaveBeenCalledTimes(2);

0 commit comments

Comments
 (0)