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:
@@ -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.
|
||||
@@ -373,6 +373,7 @@ export class Git {
|
||||
static fromAuth: (options: {
|
||||
username?: string;
|
||||
password?: string;
|
||||
token?: string;
|
||||
logger?: Logger;
|
||||
}) => Git;
|
||||
// (undocumented)
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user