From d0d4d2ebef2f66e80ac53ba0525f97f50f392214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9amus=20=C3=93=20Ceanainn?= Date: Mon, 19 Feb 2024 15:49:45 +0000 Subject: [PATCH] Support custom auth schemes for Github API (needed for proxies) --- packages/core/src/backends/github/API.ts | 7 +++-- .../src/backends/github/__tests__/API.spec.ts | 29 +++++++++++++++++++ .../src/backends/github/implementation.tsx | 14 +++++---- packages/core/src/interface.ts | 3 ++ 4 files changed, 46 insertions(+), 7 deletions(-) diff --git a/packages/core/src/backends/github/API.ts b/packages/core/src/backends/github/API.ts index a5b70bfd3..e9b04749a 100644 --- a/packages/core/src/backends/github/API.ts +++ b/packages/core/src/backends/github/API.ts @@ -33,7 +33,7 @@ import { } from '@staticcms/core/lib/util/APIUtils'; import { GitHubCommitStatusState, PullRequestState } from './types'; -import type { DataFile, PersistOptions, UnpublishedEntry } from '@staticcms/core'; +import type { AuthScheme, DataFile, PersistOptions, UnpublishedEntry } from '@staticcms/core'; import type { ApiRequest, FetchError } from '@staticcms/core/lib/util'; import type AssetProxy from '@staticcms/core/valueObjects/AssetProxy'; import type { Semaphore } from 'semaphore'; @@ -75,6 +75,7 @@ export const MOCK_PULL_REQUEST = -1; export interface Config { apiRoot?: string; token?: string; + authScheme?: AuthScheme; branch?: string; useOpenAuthoring?: boolean; openAuthoringEnabled?: boolean; @@ -162,6 +163,7 @@ export type Diff = { export default class API { apiRoot: string; token: string; + authScheme: AuthScheme; branch: string; useOpenAuthoring?: boolean; openAuthoringEnabled?: boolean; @@ -185,6 +187,7 @@ export default class API { constructor(config: Config) { this.apiRoot = config.apiRoot || 'https://api.github.com'; this.token = config.token || ''; + this.authScheme = config.authScheme || 'token'; this.branch = config.branch || 'main'; this.useOpenAuthoring = config.useOpenAuthoring; this.repo = config.repo || ''; @@ -242,7 +245,7 @@ export default class API { }; if (this.token) { - baseHeader.Authorization = `token ${this.token}`; + baseHeader.Authorization = `${this.authScheme} ${this.token}`; return Promise.resolve(baseHeader); } diff --git a/packages/core/src/backends/github/__tests__/API.spec.ts b/packages/core/src/backends/github/__tests__/API.spec.ts index 5b8d87404..dc658ef4a 100644 --- a/packages/core/src/backends/github/__tests__/API.spec.ts +++ b/packages/core/src/backends/github/__tests__/API.spec.ts @@ -103,6 +103,35 @@ describe('github API', () => { }); }); + it('should fetch url with authorization header using custom auth scheme', async () => { + const api = new API({ + branch: 'gh-pages', + repo: 'my-repo', + token: 'token', + authScheme: 'Bearer', + squashMerges: false, + initialWorkflowStatus: WorkflowStatus.DRAFT, + cmsLabelPrefix: '', + }); + + fetch.mockResolvedValue({ + text: jest.fn().mockResolvedValue('some response'), + ok: true, + status: 200, + headers: { get: () => '' }, + }); + const result = await api.request('/some-path'); + expect(result).toEqual('some response'); + expect(fetch).toHaveBeenCalledTimes(1); + expect(fetch).toHaveBeenCalledWith('https://api.github.com/some-path', { + cache: 'no-cache', + headers: { + Authorization: 'Bearer token', + 'Content-Type': 'application/json; charset=utf-8', + }, + }); + }); + it('should throw error on not ok response', async () => { const api = new API({ branch: 'gh-pages', diff --git a/packages/core/src/backends/github/implementation.tsx b/packages/core/src/backends/github/implementation.tsx index 65dce4ec2..fb6eaee28 100644 --- a/packages/core/src/backends/github/implementation.tsx +++ b/packages/core/src/backends/github/implementation.tsx @@ -25,6 +25,7 @@ import API, { API_NAME } from './API'; import AuthenticationPage from './AuthenticationPage'; import type { + AuthScheme, BackendClass, BackendEntry, ConfigWithDefaults, @@ -75,6 +76,7 @@ export default class GitHub implements BackendClass { mediaFolder?: string; previewContext: string; token: string | null; + authScheme: AuthScheme; squashMerges: boolean; cmsLabelPrefix: string; _currentUserPromise?: Promise; @@ -114,6 +116,7 @@ export default class GitHub implements BackendClass { this.branch = config.backend.branch?.trim() || 'main'; this.apiRoot = config.backend.api_root || 'https://api.github.com'; this.token = ''; + this.authScheme = config.backend.auth_scheme || 'token'; this.squashMerges = config.backend.squash_merges || false; this.cmsLabelPrefix = config.backend.cms_label_prefix || ''; this.mediaFolder = config.media_folder; @@ -171,7 +174,7 @@ export default class GitHub implements BackendClass { let repoExists = false; while (!repoExists) { repoExists = await fetch(`${this.apiRoot}/repos/${repo}`, { - headers: { Authorization: `token ${token}` }, + headers: { Authorization: `${this.authScheme} ${token}` }, }) .then(() => true) .catch(err => { @@ -194,7 +197,7 @@ export default class GitHub implements BackendClass { if (!this._currentUserPromise) { this._currentUserPromise = fetch(`${this.apiRoot}/user`, { headers: { - Authorization: `token ${token}`, + Authorization: `${this.authScheme} ${token}`, }, }).then(res => res.json()); } @@ -215,7 +218,7 @@ export default class GitHub implements BackendClass { `${this.apiRoot}/repos/${this.originRepo}/collaborators/${username}/permission`, { headers: { - Authorization: `token ${token}`, + Authorization: `${this.authScheme} ${token}`, }, }, ) @@ -232,7 +235,7 @@ export default class GitHub implements BackendClass { const repo = await fetch(`${this.apiRoot}/repos/${currentUser.login}/${repoName}`, { method: 'GET', headers: { - Authorization: `token ${token}`, + Authorization: `${this.authScheme} ${token}`, }, }).then(res => res.json()); @@ -276,7 +279,7 @@ export default class GitHub implements BackendClass { const fork = await fetch(`${this.apiRoot}/repos/${this.originRepo}/forks`, { method: 'POST', headers: { - Authorization: `token ${token}`, + Authorization: `${this.authScheme} ${token}`, }, }).then(res => res.json()); this.useOpenAuthoring = true; @@ -289,6 +292,7 @@ export default class GitHub implements BackendClass { const apiCtor = API; this.api = new apiCtor({ token: this.token, + authScheme: this.authScheme, branch: this.branch, repo: this.repo, originRepo: this.originRepo, diff --git a/packages/core/src/interface.ts b/packages/core/src/interface.ts index 5479331c0..47fa3f91c 100644 --- a/packages/core/src/interface.ts +++ b/packages/core/src/interface.ts @@ -698,6 +698,8 @@ export interface SelectWidgetOptionObject { export type AuthScope = 'repo' | 'public_repo'; +export type AuthScheme = 'token' | 'Bearer'; + export type SlugEncoding = 'unicode' | 'ascii'; export type RenderedField = F & { @@ -1025,6 +1027,7 @@ export interface Backend { identity_url?: string; gateway_url?: string; auth_scope?: AuthScope; + auth_scheme?: AuthScheme; commit_messages?: { create?: string; update?: string;