diff --git a/.changeset/tiny-cobras-look.md b/.changeset/tiny-cobras-look.md
new file mode 100644
index 0000000000..1c5acc6c4a
--- /dev/null
+++ b/.changeset/tiny-cobras-look.md
@@ -0,0 +1,5 @@
+---
+'@backstage/backend-common': patch
+---
+
+Expose an `onAuth` handler for `git` actions to provide custom credentials
diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md
index f0fa15efb0..ef8094eb84 100644
--- a/packages/backend-common/api-report.md
+++ b/packages/backend-common/api-report.md
@@ -7,6 +7,7 @@
///
import { AppConfig } from '@backstage/config';
+import { AuthCallback } from 'isomorphic-git';
import { AwsCredentialsManager } from '@backstage/integration-aws-node';
import { AwsS3Integration } from '@backstage/integration';
import { AzureDevOpsCredentialsProvider } from '@backstage/integration';
@@ -66,6 +67,12 @@ import { V1PodTemplateSpec } from '@kubernetes/client-node';
import * as winston from 'winston';
import { Writable } from 'stream';
+// @public
+export type AuthCallbackOptions = {
+ onAuth: AuthCallback;
+ logger?: LoggerService;
+};
+
// @public
export class AwsS3UrlReader implements UrlReader {
constructor(
@@ -379,12 +386,7 @@ export class Git {
deleteRemote(options: { dir: string; remote: string }): Promise;
fetch(options: { dir: string; remote?: string }): Promise;
// (undocumented)
- static fromAuth: (options: {
- username?: string;
- password?: string;
- token?: string;
- logger?: LoggerService;
- }) => Git;
+ static fromAuth: (options: StaticAuthOptions | AuthCallbackOptions) => Git;
// (undocumented)
init(options: { dir: string; defaultBranch?: string }): Promise;
log(options: { dir: string; ref?: string }): Promise;
@@ -749,6 +751,14 @@ export function setRootLogger(newLogger: winston.Logger): void;
// @public @deprecated
export const SingleHostDiscovery: typeof HostDiscovery;
+// @public
+export type StaticAuthOptions = {
+ username?: string;
+ password?: string;
+ token?: string;
+ logger?: LoggerService;
+};
+
// @public
export type StatusCheck = () => Promise;
diff --git a/packages/backend-common/src/scm/git.test.ts b/packages/backend-common/src/scm/git.test.ts
index 629173f96e..654336e920 100644
--- a/packages/backend-common/src/scm/git.test.ts
+++ b/packages/backend-common/src/scm/git.test.ts
@@ -190,7 +190,7 @@ describe('Git', () => {
});
});
- it('should pass a function that returns the authorization as the onAuth handler', async () => {
+ it('should pass a function that returns the authorization as the onAuth handler when username and password are specified', async () => {
const url = 'http://github.com/some/repo';
const dir = '/some/mock/dir';
const auth = {
@@ -208,6 +208,25 @@ describe('Git', () => {
expect(onAuth()).toEqual(auth);
});
+ it('should pass the provided callback as the onAuth handler when on auth is specified', async () => {
+ const url = 'http://github.com/some/repo';
+ const dir = '/some/mock/dir';
+ const auth = {
+ username: 'from',
+ password: 'callback',
+ };
+
+ const git = Git.fromAuth({ onAuth: () => auth });
+
+ await git.clone({ url, dir });
+
+ const { onAuth } = (
+ isomorphic.clone as unknown as jest.Mock<(typeof isomorphic)['clone']>
+ ).mock.calls[0][0]!;
+
+ expect(onAuth()).toEqual(auth);
+ });
+
it('should propagate the data from the error handler', async () => {
const url = 'http://github.com/some/repo';
const dir = '/some/mock/dir';
diff --git a/packages/backend-common/src/scm/git.ts b/packages/backend-common/src/scm/git.ts
index 234ae57d30..deb8c111cd 100644
--- a/packages/backend-common/src/scm/git.ts
+++ b/packages/backend-common/src/scm/git.ts
@@ -18,11 +18,40 @@ import git, {
ProgressCallback,
MergeResult,
ReadCommitResult,
+ AuthCallback,
} from 'isomorphic-git';
import http from 'isomorphic-git/http/node';
import fs from 'fs-extra';
import { LoggerService } from '@backstage/backend-plugin-api';
+function isAuthCallbackOptions(
+ options: StaticAuthOptions | AuthCallbackOptions,
+): options is AuthCallbackOptions {
+ return 'onAuth' in options;
+}
+
+/**
+ * Configure static credential for authentication
+ *
+ * @public
+ */
+export type StaticAuthOptions = {
+ username?: string;
+ password?: string;
+ token?: string;
+ logger?: LoggerService;
+};
+
+/**
+ * Configure an authentication callback that can provide credentials on demand
+ *
+ * @public
+ */
+export type AuthCallbackOptions = {
+ onAuth: AuthCallback;
+ logger?: LoggerService;
+};
+
/*
provider username password
Azure 'notempty' token
@@ -42,6 +71,7 @@ instead of Basic Auth (e.g., Bitbucket Server).
*
* @public
*/
+
export class Git {
private readonly headers: {
[x: string]: string;
@@ -49,12 +79,13 @@ export class Git {
private constructor(
private readonly config: {
- username?: string;
- password?: string;
+ onAuth: AuthCallback;
token?: string;
logger?: LoggerService;
},
) {
+ this.onAuth = config.onAuth;
+
this.headers = {
'user-agent': 'git/@isomorphic-git',
...(config.token ? { Authorization: `Bearer ${config.token}` } : {}),
@@ -283,10 +314,7 @@ export class Git {
});
}
- private onAuth = () => ({
- username: this.config.username,
- password: this.config.password,
- });
+ private onAuth: AuthCallback;
private onProgressHandler = (): ProgressCallback => {
let currentPhase = '';
@@ -303,13 +331,13 @@ export class Git {
};
};
- static fromAuth = (options: {
- username?: string;
- password?: string;
- token?: string;
- logger?: LoggerService;
- }) => {
+ static fromAuth = (options: StaticAuthOptions | AuthCallbackOptions) => {
+ if (isAuthCallbackOptions(options)) {
+ const { onAuth, logger } = options;
+ return new Git({ onAuth, logger });
+ }
+
const { username, password, token, logger } = options;
- return new Git({ username, password, token, logger });
+ return new Git({ onAuth: () => ({ username, password }), token, logger });
};
}
diff --git a/packages/backend-common/src/scm/index.ts b/packages/backend-common/src/scm/index.ts
index ceba752c9e..f9c59e99eb 100644
--- a/packages/backend-common/src/scm/index.ts
+++ b/packages/backend-common/src/scm/index.ts
@@ -15,3 +15,4 @@
*/
export { Git } from './git';
+export type { StaticAuthOptions, AuthCallbackOptions } from './git';