feat: support Bearer auth header at git commands

Support Bearer-based Authorization header for auth
with token only (no username).

Signed-off-by: Patrick Jungermann <Patrick.Jungermann@gmail.com>
This commit is contained in:
Patrick Jungermann
2022-07-22 12:33:02 +02:00
parent 593dea6710
commit 3b7930b3e5
6 changed files with 145 additions and 26 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/plugin-scaffolder-backend': minor
'@backstage/backend-common': patch
---
Add support for Bearer Authorization header / token-based auth at Git commands.
+1
View File
@@ -373,6 +373,7 @@ export class Git {
static fromAuth: (options: {
username?: string;
password?: string;
token?: string;
logger?: Logger;
}) => Git;
// (undocumented)
+86 -3
View File
@@ -26,6 +26,7 @@ describe('Git', () => {
beforeEach(() => {
jest.resetAllMocks();
});
describe('add', () => {
it('should call isomorphic-git add with the correct arguments', async () => {
const git = Git.fromAuth({});
@@ -146,6 +147,33 @@ describe('Git', () => {
onAuth: expect.any(Function),
});
});
it('should call isomorphic-git with the correct arguments (Bearer)', async () => {
const url = 'http://github.com/some/repo';
const dir = '/some/mock/dir';
const auth = {
token: 'test',
};
const git = Git.fromAuth(auth);
await git.clone({ url, dir });
expect(isomorphic.clone).toHaveBeenCalledWith({
fs,
http,
url,
dir,
singleBranch: true,
depth: 1,
onProgress: expect.any(Function),
headers: {
Authorization: 'Bearer test',
'user-agent': 'git/@isomorphic-git',
},
onAuth: expect.any(Function),
});
});
it('should pass a function that returns the authorization as the onAuth handler', async () => {
const url = 'http://github.com/some/repo';
const dir = '/some/mock/dir';
@@ -164,7 +192,7 @@ describe('Git', () => {
expect(onAuth()).toEqual(auth);
});
it('should propogate the data from the error handler', async () => {
it('should propagate the data from the error handler', async () => {
const url = 'http://github.com/some/repo';
const dir = '/some/mock/dir';
const auth = {
@@ -234,6 +262,31 @@ describe('Git', () => {
onAuth: expect.any(Function),
});
});
it('should call isomorphic-git with the correct arguments (Bearer)', async () => {
const remote = 'http://github.com/some/repo';
const dir = '/some/mock/dir';
const auth = {
token: 'test',
};
const git = Git.fromAuth(auth);
await git.fetch({ remote, dir });
expect(isomorphic.fetch).toHaveBeenCalledWith({
fs,
http,
remote,
dir,
onProgress: expect.any(Function),
headers: {
Authorization: 'Bearer test',
'user-agent': 'git/@isomorphic-git',
},
onAuth: expect.any(Function),
});
});
it('should pass a function that returns the authorization as the onAuth handler', async () => {
const remote = 'http://github.com/some/repo';
const dir = '/some/mock/dir';
@@ -252,7 +305,7 @@ describe('Git', () => {
expect(onAuth()).toEqual(auth);
});
it('should propogate the data from the error handler', async () => {
it('should propagate the data from the error handler', async () => {
const remote = 'http://github.com/some/repo';
const dir = '/some/mock/dir';
const auth = {
@@ -348,6 +401,35 @@ describe('Git', () => {
onAuth: expect.any(Function),
});
});
it('should call isomorphic-git with the correct arguments (Bearer)', async () => {
const remote = 'origin';
const dir = '/some/mock/dir';
const auth = {
token: 'test',
};
const git = Git.fromAuth(auth);
const remoteRef = 'master';
const force = true;
await git.push({ dir, remote, remoteRef, force });
expect(isomorphic.push).toHaveBeenCalledWith({
fs,
http,
remote,
dir,
remoteRef,
force,
onProgress: expect.any(Function),
headers: {
Authorization: 'Bearer test',
'user-agent': 'git/@isomorphic-git',
},
onAuth: expect.any(Function),
});
});
it('should call isomorphic-git with remoteRef parameter', async () => {
const remote = 'origin';
const remoteRef = 'refs/for/master';
@@ -373,6 +455,7 @@ describe('Git', () => {
onAuth: expect.any(Function),
});
});
it('should pass a function that returns the authorization as the onAuth handler', async () => {
const remote = 'origin';
const dir = '/some/mock/dir';
@@ -393,7 +476,7 @@ describe('Git', () => {
expect(onAuth()).toEqual(auth);
});
it('should propogate the data from the error handler', async () => {
it('should propagate the data from the error handler', async () => {
const remote = 'origin';
const dir = '/some/mock/dir';
const auth = {
+26 -15
View File
@@ -24,13 +24,17 @@ import fs from 'fs-extra';
import { Logger } from 'winston';
/*
provider username password
GitHub 'x-access-token' token
BitBucket 'x-token-auth' token
GitLab 'oauth2' token
provider username password
Azure 'notempty' token
Bitbucket Cloud 'x-token-auth' token
Bitbucket Server username password or token
GitHub 'x-access-token' token
GitLab 'oauth2' token
From : https://isomorphic-git.org/docs/en/onAuth with fix for GitHub
Azure 'notempty' token
Or token provided as `token` for Bearer auth header
instead of Basic Auth (e.g., Bitbucket Server).
*/
/**
@@ -39,13 +43,23 @@ Azure 'notempty' token
* @public
*/
export class Git {
private readonly headers: {
[x: string]: string;
};
private constructor(
private readonly config: {
username?: string;
password?: string;
token?: string;
logger?: Logger;
},
) {}
) {
this.headers = {
'user-agent': 'git/@isomorphic-git',
...(config.token ? { Authorization: `Bearer ${config.token}` } : {}),
};
}
async add(options: { dir: string; filepath: string }): Promise<void> {
const { dir, filepath } = options;
@@ -116,9 +130,7 @@ export class Git {
depth: depth ?? 1,
noCheckout,
onProgress: this.onProgressHandler(),
headers: {
'user-agent': 'git/@isomorphic-git',
},
headers: this.headers,
onAuth: this.onAuth,
});
} catch (ex) {
@@ -155,7 +167,7 @@ export class Git {
dir,
remote,
onProgress: this.onProgressHandler(),
headers: { 'user-agent': 'git/@isomorphic-git' },
headers: this.headers,
onAuth: this.onAuth,
});
} catch (ex) {
@@ -222,9 +234,7 @@ export class Git {
onProgress: this.onProgressHandler(),
remoteRef,
force,
headers: {
'user-agent': 'git/@isomorphic-git',
},
headers: this.headers,
remote,
onAuth: this.onAuth,
});
@@ -290,9 +300,10 @@ export class Git {
static fromAuth = (options: {
username?: string;
password?: string;
token?: string;
logger?: Logger;
}) => {
const { username, password, logger } = options;
return new Git({ username, password, logger });
const { username, password, token, logger } = options;
return new Git({ username, password, token, logger });
};
}
@@ -33,8 +33,6 @@ jest.mock('@backstage/backend-common', () => ({
}));
const mockedGit = Git.fromAuth({
username: 'test-user',
password: 'test-password',
logger: getVoidLogger(),
});
@@ -101,6 +99,22 @@ describe('initRepoAndPush', () => {
});
});
it('with token', async () => {
await initRepoAndPush({
dir: '/test/repo/dir/',
remoteUrl: 'git@github.com:test/repo.git',
auth: {
token: 'test-token',
},
logger: getVoidLogger(),
});
expect(mockedGit.init).toHaveBeenCalledWith({
dir: '/test/repo/dir/',
defaultBranch: 'master',
});
});
it('allows overriding the default branch', async () => {
await initRepoAndPush({
dir: '/test/repo/dir/',
@@ -83,15 +83,17 @@ export async function initRepoAndPush({
}: {
dir: string;
remoteUrl: string;
auth: { username: string; password: string };
// For use cases where token has to be used with Basic Auth
// it has to be provided as password together with a username
// which may be a fixed value defined by the provider.
auth: { username: string; password: string } | { token: string };
logger: Logger;
defaultBranch?: string;
commitMessage?: string;
gitAuthorInfo?: { name?: string; email?: string };
}): Promise<void> {
const git = Git.fromAuth({
username: auth.username,
password: auth.password,
...auth,
logger,
});
@@ -137,7 +139,10 @@ export async function commitAndPushRepo({
remoteRef,
}: {
dir: string;
auth: { username: string; password: string };
// For use cases where token has to be used with Basic Auth
// it has to be provided as password together with a username
// which may be a fixed value defined by the provider.
auth: { username: string; password: string } | { token: string };
logger: Logger;
commitMessage: string;
gitAuthorInfo?: { name?: string; email?: string };
@@ -145,8 +150,7 @@ export async function commitAndPushRepo({
remoteRef?: string;
}): Promise<void> {
const git = Git.fromAuth({
username: auth.username,
password: auth.password,
...auth,
logger,
});