feat: Support custom auth schemes for Github API (#1086)

This commit is contained in:
Séamus Ó Ceanainn 2024-03-05 16:06:15 +00:00 committed by GitHub
parent dcdc49232d
commit 17b32f0902
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 46 additions and 7 deletions

View File

@ -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);
}

View File

@ -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',

View File

@ -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<GitHubUser>;
@ -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,

View File

@ -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 extends BaseField = UnknownField> = 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;