Fix SingleInstanceGithubCredentialsProvider to return app credentials for bare host URLs
Signed-off-by: Vincenzo Scamporlino <vincenzos@spotify.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/integration': patch
|
||||
---
|
||||
|
||||
Fixed `SingleInstanceGithubCredentialsProvider` to return app credentials when `getCredentials` is called with a bare host URL (e.g. `https://github.com`) instead of falling back to a personal access token.
|
||||
@@ -25,6 +25,12 @@ const octokit = {
|
||||
},
|
||||
};
|
||||
|
||||
const mockCreateAppAuth = jest.fn();
|
||||
|
||||
jest.mock('@octokit/auth-app', () => ({
|
||||
createAppAuth: (...args: any[]) => mockCreateAppAuth(...args),
|
||||
}));
|
||||
|
||||
jest.mock('@octokit/rest', () => {
|
||||
class Octokit {
|
||||
constructor() {
|
||||
@@ -43,6 +49,12 @@ describe('SingleInstanceGithubCredentialsProvider tests', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
mockCreateAppAuth.mockReturnValue(async (opts: { type: string }) => {
|
||||
if (opts.type === 'app') {
|
||||
return { token: 'mock-jwt-token' };
|
||||
}
|
||||
throw new Error(`Unexpected auth type: ${opts.type}`);
|
||||
});
|
||||
github = SingleInstanceGithubCredentialsProvider.create({
|
||||
host: 'github.com',
|
||||
apps: [
|
||||
@@ -889,4 +901,76 @@ describe('SingleInstanceGithubCredentialsProvider tests', () => {
|
||||
expect(token).toEqual('public_access_from_app_2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('bare host URL (no org/repo)', () => {
|
||||
it('should return app JWT when URL has no org or repo', async () => {
|
||||
const { token, headers, type } = await github.getCredentials({
|
||||
url: 'https://github.com',
|
||||
});
|
||||
|
||||
expect(type).toEqual('app');
|
||||
expect(token).toEqual('mock-jwt-token');
|
||||
expect(headers).toEqual({
|
||||
Authorization: 'Bearer mock-jwt-token',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return app JWT with multiple apps configured', async () => {
|
||||
const multiAppProvider = SingleInstanceGithubCredentialsProvider.create({
|
||||
host: 'github.com',
|
||||
apps: [
|
||||
{
|
||||
appId: 1,
|
||||
privateKey: 'privateKey',
|
||||
webhookSecret: '123',
|
||||
clientId: 'CLIENT_ID',
|
||||
clientSecret: 'CLIENT_SECRET',
|
||||
},
|
||||
{
|
||||
appId: 2,
|
||||
privateKey: 'privateKey2',
|
||||
webhookSecret: '456',
|
||||
clientId: 'CLIENT_ID_2',
|
||||
clientSecret: 'CLIENT_SECRET_2',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { token, type } = await multiAppProvider.getCredentials({
|
||||
url: 'https://github.com',
|
||||
});
|
||||
|
||||
expect(type).toEqual('app');
|
||||
expect(token).toBeDefined();
|
||||
});
|
||||
|
||||
it('should fall back to configured token when no apps are configured and URL has no org', async () => {
|
||||
const githubProvider = SingleInstanceGithubCredentialsProvider.create({
|
||||
host: 'github.com',
|
||||
apps: [],
|
||||
token: 'fallback_token',
|
||||
});
|
||||
|
||||
const { token, type } = await githubProvider.getCredentials({
|
||||
url: 'https://github.com',
|
||||
});
|
||||
|
||||
expect(type).toEqual('token');
|
||||
expect(token).toEqual('fallback_token');
|
||||
});
|
||||
|
||||
it('should return undefined token when no apps and no token configured for bare host URL', async () => {
|
||||
const githubProvider = SingleInstanceGithubCredentialsProvider.create({
|
||||
host: 'github.com',
|
||||
});
|
||||
|
||||
const { token, headers, type } = await githubProvider.getCredentials({
|
||||
url: 'https://github.com',
|
||||
});
|
||||
|
||||
expect(type).toEqual('token');
|
||||
expect(token).toBeUndefined();
|
||||
expect(headers).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -124,6 +124,17 @@ class GithubAppManager {
|
||||
owner: string,
|
||||
repo?: string,
|
||||
): Promise<{ accessToken: string | undefined }> {
|
||||
// No owner means a bare host URL (e.g. https://github.com) — return an
|
||||
// app-level JWT rather than an installation token.
|
||||
if (!owner) {
|
||||
const auth = createAppAuth({
|
||||
appId: this.baseAuthConfig.appId,
|
||||
privateKey: this.baseAuthConfig.privateKey,
|
||||
});
|
||||
const { token } = await auth({ type: 'app' });
|
||||
return { accessToken: token };
|
||||
}
|
||||
|
||||
if (this.allowedInstallationOwners) {
|
||||
if (
|
||||
!this.allowedInstallationOwners?.includes(
|
||||
|
||||
Reference in New Issue
Block a user