Add fetchApiRef which implements fetch, plus Backstage token header when available.
This intends to be the basis for other plugins' data fetching needs, so that they can transparently interact with the catalog and other parts of the Backstage ecosystem without explicitly having to deal with authenticating themselves. Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/app-defaults': patch
|
||||
'@backstage/core-app-api': patch
|
||||
'@backstage/core-plugin-api': patch
|
||||
---
|
||||
|
||||
Add `FetchApi` and related `fetchApiRef` which implement fetch, with an added Backstage token header when available.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/catalog-client': patch
|
||||
---
|
||||
|
||||
Add the ability to supply a custom `fetchApi`. In the default frontend app setup, this will use the `fetchApiRef` implementation.
|
||||
@@ -34,6 +34,9 @@ import {
|
||||
OneLoginAuth,
|
||||
UnhandledErrorForwarder,
|
||||
AtlassianAuth,
|
||||
FetchApiBuilder,
|
||||
IdentityAwareFetchMiddleware,
|
||||
BackstageProtocolResolverFetchMiddleware,
|
||||
} from '@backstage/core-app-api';
|
||||
|
||||
import {
|
||||
@@ -42,6 +45,8 @@ import {
|
||||
analyticsApiRef,
|
||||
errorApiRef,
|
||||
discoveryApiRef,
|
||||
fetchApiRef,
|
||||
identityApiRef,
|
||||
oauthRequestApiRef,
|
||||
googleAuthApiRef,
|
||||
githubAuthApiRef,
|
||||
@@ -92,6 +97,24 @@ export const apis = [
|
||||
deps: { errorApi: errorApiRef },
|
||||
factory: ({ errorApi }) => WebStorage.create({ errorApi }),
|
||||
}),
|
||||
createApiFactory({
|
||||
api: fetchApiRef,
|
||||
deps: { identityApi: identityApiRef, discoveryApi: discoveryApiRef },
|
||||
factory: ({ identityApi, discoveryApi }) => {
|
||||
return FetchApiBuilder.create()
|
||||
.with(
|
||||
new IdentityAwareFetchMiddleware(() => {
|
||||
return identityApi.getCredentials().then(r => r.token);
|
||||
}),
|
||||
)
|
||||
.with(
|
||||
new BackstageProtocolResolverFetchMiddleware(plugin => {
|
||||
return discoveryApi.getBaseUrl(plugin);
|
||||
}),
|
||||
)
|
||||
.build();
|
||||
},
|
||||
}),
|
||||
createApiFactory({
|
||||
api: oauthRequestApiRef,
|
||||
deps: {},
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
```ts
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { EntityName } from '@backstage/catalog-model';
|
||||
import { default as fetch_2 } from 'cross-fetch';
|
||||
import { Location as Location_2 } from '@backstage/catalog-model';
|
||||
|
||||
// @public
|
||||
@@ -71,7 +72,7 @@ export interface CatalogApi {
|
||||
|
||||
// @public
|
||||
export class CatalogClient implements CatalogApi {
|
||||
constructor(options: { discoveryApi: DiscoveryApi });
|
||||
constructor(options: { discoveryApi: DiscoveryApi; fetchApi?: FetchApi });
|
||||
addLocation(
|
||||
{ type, target, dryRun, presence }: AddLocationRequest,
|
||||
options?: CatalogRequestOptions,
|
||||
@@ -155,4 +156,9 @@ export type DiscoveryApi = {
|
||||
// @public
|
||||
export const ENTITY_STATUS_CATALOG_PROCESSING_TYPE =
|
||||
'backstage.io/catalog-processing';
|
||||
|
||||
// @public
|
||||
export type FetchApi = {
|
||||
fetch: typeof fetch_2;
|
||||
};
|
||||
```
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
stringifyLocationReference,
|
||||
} from '@backstage/catalog-model';
|
||||
import { ResponseError } from '@backstage/errors';
|
||||
import fetch from 'cross-fetch';
|
||||
import crossFetch from 'cross-fetch';
|
||||
import {
|
||||
CATALOG_FILTER_EXISTS,
|
||||
AddLocationRequest,
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
CatalogEntityAncestorsResponse,
|
||||
} from './types/api';
|
||||
import { DiscoveryApi } from './types/discovery';
|
||||
import { FetchApi } from './types/fetch';
|
||||
|
||||
/**
|
||||
* A frontend and backend compatible client for communicating with the Backstage Catalog.
|
||||
@@ -46,9 +47,11 @@ import { DiscoveryApi } from './types/discovery';
|
||||
* */
|
||||
export class CatalogClient implements CatalogApi {
|
||||
private readonly discoveryApi: DiscoveryApi;
|
||||
private readonly fetchApi: FetchApi;
|
||||
|
||||
constructor(options: { discoveryApi: DiscoveryApi }) {
|
||||
constructor(options: { discoveryApi: DiscoveryApi; fetchApi?: FetchApi }) {
|
||||
this.discoveryApi = options.discoveryApi;
|
||||
this.fetchApi = options.fetchApi || { fetch: crossFetch };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -206,7 +209,7 @@ export class CatalogClient implements CatalogApi {
|
||||
* @public
|
||||
*/
|
||||
async refreshEntity(entityRef: string, options?: CatalogRequestOptions) {
|
||||
const response = await fetch(
|
||||
const response = await this.fetchApi.fetch(
|
||||
`${await this.discoveryApi.getBaseUrl('catalog')}/refresh`,
|
||||
{
|
||||
headers: {
|
||||
@@ -237,7 +240,7 @@ export class CatalogClient implements CatalogApi {
|
||||
{ type = 'url', target, dryRun, presence }: AddLocationRequest,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<AddLocationResponse> {
|
||||
const response = await fetch(
|
||||
const response = await this.fetchApi.fetch(
|
||||
`${await this.discoveryApi.getBaseUrl('catalog')}/locations${
|
||||
dryRun ? '?dryRun=true' : ''
|
||||
}`,
|
||||
@@ -376,7 +379,7 @@ export class CatalogClient implements CatalogApi {
|
||||
const headers: Record<string, string> = options?.token
|
||||
? { Authorization: `Bearer ${options.token}` }
|
||||
: {};
|
||||
const response = await fetch(url, { method, headers });
|
||||
const response = await this.fetchApi.fetch(url, { method, headers });
|
||||
|
||||
if (!response.ok) {
|
||||
throw await ResponseError.fromResponse(response);
|
||||
@@ -392,7 +395,7 @@ export class CatalogClient implements CatalogApi {
|
||||
const headers: Record<string, string> = options?.token
|
||||
? { Authorization: `Bearer ${options.token}` }
|
||||
: {};
|
||||
const response = await fetch(url, { method, headers });
|
||||
const response = await this.fetchApi.fetch(url, { method, headers });
|
||||
|
||||
if (!response.ok) {
|
||||
throw await ResponseError.fromResponse(response);
|
||||
@@ -410,7 +413,7 @@ export class CatalogClient implements CatalogApi {
|
||||
const headers: Record<string, string> = options?.token
|
||||
? { Authorization: `Bearer ${options.token}` }
|
||||
: {};
|
||||
const response = await fetch(url, { method, headers });
|
||||
const response = await this.fetchApi.fetch(url, { method, headers });
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 404) {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is a copy of the core DiscoveryApi, to avoid importing core.
|
||||
* This is a copy of the DiscoveryApi, to avoid importing core-plugin-api.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import fetch from 'cross-fetch';
|
||||
|
||||
/**
|
||||
* This is a copy of FetchApi, to avoid importing core-plugin-api.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type FetchApi = {
|
||||
fetch: typeof fetch;
|
||||
};
|
||||
@@ -25,5 +25,6 @@ export type {
|
||||
CatalogEntityAncestorsResponse,
|
||||
} from './api';
|
||||
export type { DiscoveryApi } from './discovery';
|
||||
export type { FetchApi } from './fetch';
|
||||
export { CATALOG_FILTER_EXISTS } from './api';
|
||||
export { ENTITY_STATUS_CATALOG_PROCESSING_TYPE } from './status';
|
||||
|
||||
@@ -26,6 +26,7 @@ import { bitbucketAuthApiRef } from '@backstage/core-plugin-api';
|
||||
import { ComponentType } from 'react';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { createApp as createApp_2 } from '@backstage/app-defaults';
|
||||
import crossFetch from 'cross-fetch';
|
||||
import { DiscoveryApi } from '@backstage/core-plugin-api';
|
||||
import { ErrorApi } from '@backstage/core-plugin-api';
|
||||
import { ErrorApiError } from '@backstage/core-plugin-api';
|
||||
@@ -34,6 +35,7 @@ import { ExternalRouteRef } from '@backstage/core-plugin-api';
|
||||
import { FeatureFlag } from '@backstage/core-plugin-api';
|
||||
import { FeatureFlagsApi } from '@backstage/core-plugin-api';
|
||||
import { FeatureFlagsSaveOptions } from '@backstage/core-plugin-api';
|
||||
import { FetchApi } from '@backstage/core-plugin-api';
|
||||
import { gitlabAuthApiRef } from '@backstage/core-plugin-api';
|
||||
import { googleAuthApiRef } from '@backstage/core-plugin-api';
|
||||
import { IconComponent } from '@backstage/core-plugin-api';
|
||||
@@ -288,6 +290,14 @@ export type BackstagePluginWithAnyOutput = Omit<
|
||||
)[];
|
||||
};
|
||||
|
||||
// @public
|
||||
export class BackstageProtocolResolverFetchMiddleware
|
||||
implements FetchMiddleware
|
||||
{
|
||||
constructor(discovery: (pluginId: string) => Promise<string>);
|
||||
apply(next: FetchFunction): FetchFunction;
|
||||
}
|
||||
|
||||
// @public
|
||||
export class BitbucketAuth {
|
||||
// (undocumented)
|
||||
@@ -369,6 +379,24 @@ export type FeatureFlaggedProps = {
|
||||
}
|
||||
);
|
||||
|
||||
// @public
|
||||
export class FetchApiBuilder {
|
||||
// (undocumented)
|
||||
build(): FetchApi;
|
||||
// (undocumented)
|
||||
static create(): FetchApiBuilder;
|
||||
// (undocumented)
|
||||
with(middleware: FetchMiddleware): FetchApiBuilder;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type FetchFunction = typeof crossFetch;
|
||||
|
||||
// @public
|
||||
export interface FetchMiddleware {
|
||||
apply(next: FetchFunction): FetchFunction;
|
||||
}
|
||||
|
||||
// @public
|
||||
export const FlatRoutes: (props: FlatRoutesProps) => JSX.Element | null;
|
||||
|
||||
@@ -426,6 +454,13 @@ export class GoogleAuth {
|
||||
static create(options: OAuthApiCreateOptions): typeof googleAuthApiRef.T;
|
||||
}
|
||||
|
||||
// @public
|
||||
export class IdentityAwareFetchMiddleware implements FetchMiddleware {
|
||||
constructor(tokenFunction: () => Promise<string | undefined>);
|
||||
apply(next: FetchFunction): FetchFunction;
|
||||
setHeaderName(name: string): IdentityAwareFetchMiddleware;
|
||||
}
|
||||
|
||||
// @public
|
||||
export class LocalStorageFeatureFlags implements FeatureFlagsApi {
|
||||
// (undocumented)
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@types/prop-types": "^15.7.3",
|
||||
"cross-fetch": "^3.0.6",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-router-dom": "6.0.0-beta.0",
|
||||
"react-use": "^17.2.4",
|
||||
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { BackstageProtocolResolverFetchMiddleware } from './BackstageProtocolResolverFetchMiddleware';
|
||||
|
||||
describe('BackstageProtocolResolverFetchMiddleware', () => {
|
||||
it.each([['https://passthrough.com/a']])(
|
||||
'passes through regular URLs, %p',
|
||||
async (url: string) => {
|
||||
const resolve = jest.fn();
|
||||
const middleware = new BackstageProtocolResolverFetchMiddleware(resolve);
|
||||
const inner = jest.fn();
|
||||
const outer = middleware.apply(inner);
|
||||
|
||||
await outer(url);
|
||||
expect(inner.mock.calls[0][0]).toBe(url);
|
||||
expect(resolve).not.toBeCalled();
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
[
|
||||
'backstage://my-plugin/sub/path',
|
||||
'my-plugin',
|
||||
'https://real.com/base',
|
||||
'https://real.com/base/sub/path',
|
||||
],
|
||||
[
|
||||
'backstage://my-plugin/sub/path/',
|
||||
'my-plugin',
|
||||
'https://real.com/base/',
|
||||
'https://real.com/base/sub/path/',
|
||||
],
|
||||
['backstage://x', 'x', 'http://real.com:8080', 'http://real.com:8080'],
|
||||
[
|
||||
'backstage://x/a/b?c=d&e=f#g',
|
||||
'x',
|
||||
'https://real.com/base',
|
||||
'https://real.com/base/a/b?c=d&e=f#g',
|
||||
],
|
||||
[
|
||||
'backstage://x?c=d&e=f#g',
|
||||
'x',
|
||||
'https://real.com:8080/base',
|
||||
'https://real.com:8080/base?c=d&e=f#g',
|
||||
],
|
||||
[
|
||||
'backstage://username:password@x?c=d&e=f#g',
|
||||
'x',
|
||||
'https://real.com:8080/base',
|
||||
'https://username:password@real.com:8080/base?c=d&e=f#g',
|
||||
],
|
||||
[
|
||||
'backstage://x?c=d&e=f#g',
|
||||
'x',
|
||||
'https://username:password@real.com:8080/base',
|
||||
'https://username:password@real.com:8080/base?c=d&e=f#g',
|
||||
],
|
||||
])(
|
||||
'resolves backstage URLs, %p',
|
||||
async (original, host, resolved, result) => {
|
||||
const resolve = jest.fn();
|
||||
const middleware = new BackstageProtocolResolverFetchMiddleware(resolve);
|
||||
const inner = jest.fn();
|
||||
const outer = middleware.apply(inner);
|
||||
|
||||
resolve.mockResolvedValueOnce(resolved);
|
||||
await outer(original);
|
||||
expect(inner.mock.calls[0][0]).toBe(result);
|
||||
expect(resolve).toHaveBeenLastCalledWith(host);
|
||||
},
|
||||
);
|
||||
});
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { FetchFunction, FetchMiddleware } from './types';
|
||||
|
||||
function join(left: string, right: string): string {
|
||||
if (!right || right === '/') {
|
||||
return left;
|
||||
}
|
||||
|
||||
return `${left.replace(/\/$/, '')}/${right.replace(/^\//, '')}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles translation from backstage://some-plugin-id/<path> to concrete
|
||||
* http(s) URLs.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class BackstageProtocolResolverFetchMiddleware
|
||||
implements FetchMiddleware
|
||||
{
|
||||
constructor(
|
||||
private readonly discovery: (pluginId: string) => Promise<string>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* {@inheritdoc FetchMiddleware.apply}
|
||||
*/
|
||||
apply(next: FetchFunction): FetchFunction {
|
||||
return async (input, init) => {
|
||||
const request = new Request(input, init);
|
||||
const { protocol, hostname, pathname, search, hash, username, password } =
|
||||
new URL(request.url);
|
||||
|
||||
if (protocol !== 'backstage:') {
|
||||
return next(input, init);
|
||||
}
|
||||
|
||||
let base = await this.discovery(hostname);
|
||||
if (username || password) {
|
||||
const baseUrl = new URL(base);
|
||||
const authority = `${username}${password ? `:${password}` : ''}@`;
|
||||
base = `${baseUrl.protocol}//${authority}${baseUrl.host}${baseUrl.pathname}`;
|
||||
}
|
||||
|
||||
const target = `${join(base, pathname)}${search}${hash}`;
|
||||
return next(target, request);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { FetchApi } from '@backstage/core-plugin-api';
|
||||
import crossFetch from 'cross-fetch';
|
||||
import { FetchFunction, FetchMiddleware } from './types';
|
||||
|
||||
/**
|
||||
* Builds a fetch API, based on the builtin fetch wrapped by a set of optional
|
||||
* middleware implementations that add behaviors.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class FetchApiBuilder {
|
||||
static create(): FetchApiBuilder {
|
||||
return new FetchApiBuilder(crossFetch, []);
|
||||
}
|
||||
|
||||
private constructor(
|
||||
private readonly implementation: FetchFunction,
|
||||
private readonly middleware: FetchMiddleware[],
|
||||
) {}
|
||||
|
||||
with(middleware: FetchMiddleware): FetchApiBuilder {
|
||||
return new FetchApiBuilder(this.implementation, [
|
||||
...this.middleware,
|
||||
middleware,
|
||||
]);
|
||||
}
|
||||
|
||||
build(): FetchApi {
|
||||
let result = this.implementation;
|
||||
|
||||
for (const m of this.middleware) {
|
||||
result = m.apply(result);
|
||||
}
|
||||
|
||||
return {
|
||||
fetch: result,
|
||||
};
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { IdentityAwareFetchMiddleware } from './IdentityAwareFetchMiddleware';
|
||||
|
||||
describe('IdentityAwareFetchMiddleware', () => {
|
||||
it('injects the header only when a token is available', async () => {
|
||||
const tokenFunction = jest.fn();
|
||||
const middleware = new IdentityAwareFetchMiddleware(tokenFunction);
|
||||
const inner = jest.fn();
|
||||
const outer = middleware.apply(inner);
|
||||
|
||||
// No token available
|
||||
tokenFunction.mockResolvedValueOnce(undefined);
|
||||
await outer(new Request('https://example.com'));
|
||||
expect([...inner.mock.calls[0][0].headers.entries()]).toEqual([]);
|
||||
|
||||
// Supply a token, header gets added
|
||||
tokenFunction.mockResolvedValueOnce('token');
|
||||
await outer(new Request('https://example.com'));
|
||||
expect([...inner.mock.calls[1][0].headers.entries()]).toEqual([
|
||||
['backstage-token', 'token'],
|
||||
]);
|
||||
|
||||
// Token no longer available
|
||||
tokenFunction.mockResolvedValueOnce(undefined);
|
||||
await outer(new Request('https://example.com'));
|
||||
expect([...inner.mock.calls[2][0].headers.entries()]).toEqual([]);
|
||||
});
|
||||
});
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { FetchFunction, FetchMiddleware } from './types';
|
||||
|
||||
const DEFAULT_HEADER_NAME = 'backstage-token';
|
||||
|
||||
/**
|
||||
* A fetch middleware, which injects a Backstage token header when the user is
|
||||
* signed in.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class IdentityAwareFetchMiddleware implements FetchMiddleware {
|
||||
private headerName: string;
|
||||
private tokenFunction: () => Promise<string | undefined>;
|
||||
|
||||
constructor(tokenFunction: () => Promise<string | undefined>) {
|
||||
this.headerName = DEFAULT_HEADER_NAME;
|
||||
this.tokenFunction = tokenFunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc FetchMiddleware.apply}
|
||||
*/
|
||||
apply(next: FetchFunction): FetchFunction {
|
||||
return async (input, init) => {
|
||||
const token = await this.tokenFunction();
|
||||
if (typeof token !== 'string') {
|
||||
return next(input, init);
|
||||
}
|
||||
|
||||
const request = new Request(input, init);
|
||||
request.headers.set(this.headerName, token);
|
||||
return next(request);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the header name from the default value to a custom one.
|
||||
*/
|
||||
setHeaderName(name: string): IdentityAwareFetchMiddleware {
|
||||
this.headerName = name;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { BackstageProtocolResolverFetchMiddleware } from './BackstageProtocolResolverFetchMiddleware';
|
||||
export { FetchApiBuilder } from './FetchApiBuilder';
|
||||
export { IdentityAwareFetchMiddleware } from './IdentityAwareFetchMiddleware';
|
||||
export type { FetchFunction, FetchMiddleware } from './types';
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import crossFetch from 'cross-fetch';
|
||||
|
||||
/**
|
||||
* The type of a fetch call.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type FetchFunction = typeof crossFetch;
|
||||
|
||||
/**
|
||||
* A middleware that modifies the behavior of an ongoing fetch.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface FetchMiddleware {
|
||||
/**
|
||||
* Applies this middleware to an inner implementation.
|
||||
*
|
||||
* @param next - The next, inner, implementation, that this middleware shall
|
||||
* call out to as part of the request cycle.
|
||||
*/
|
||||
apply(next: FetchFunction): FetchFunction;
|
||||
}
|
||||
@@ -27,5 +27,6 @@ export * from './ConfigApi';
|
||||
export * from './DiscoveryApi';
|
||||
export * from './ErrorApi';
|
||||
export * from './FeatureFlagsApi';
|
||||
export * from './FetchApi';
|
||||
export * from './OAuthRequestApi';
|
||||
export * from './StorageApi';
|
||||
|
||||
@@ -9,6 +9,7 @@ import { BackstagePlugin as BackstagePlugin_2 } from '@backstage/core-plugin-api
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import { ComponentType } from 'react';
|
||||
import { Config } from '@backstage/config';
|
||||
import { default as fetch_2 } from 'cross-fetch';
|
||||
import { IconComponent as IconComponent_2 } from '@backstage/core-plugin-api';
|
||||
import { IdentityApi as IdentityApi_2 } from '@backstage/core-plugin-api';
|
||||
import { Observable as Observable_2 } from '@backstage/types';
|
||||
@@ -505,6 +506,14 @@ export enum FeatureFlagState {
|
||||
None = 0,
|
||||
}
|
||||
|
||||
// @public
|
||||
export type FetchApi = {
|
||||
fetch: typeof fetch_2;
|
||||
};
|
||||
|
||||
// @public
|
||||
export const fetchApiRef: ApiRef<FetchApi>;
|
||||
|
||||
// @public
|
||||
export function getComponentData<T>(
|
||||
node: ReactNode,
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
"@backstage/types": "^0.1.1",
|
||||
"@backstage/version-bridge": "^0.1.1",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"cross-fetch": "^3.0.6",
|
||||
"history": "^5.0.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-router-dom": "6.0.0-beta.0",
|
||||
@@ -56,7 +57,6 @@
|
||||
"@types/node": "^14.14.32",
|
||||
"@types/prop-types": "^15.7.3",
|
||||
"@types/zen-observable": "^0.8.0",
|
||||
"cross-fetch": "^3.0.6",
|
||||
"msw": "^0.35.0"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import fetch from 'cross-fetch';
|
||||
import { ApiRef, createApiRef } from '../system';
|
||||
|
||||
/**
|
||||
* A wrapper for the fetch API, that has additional behaviors such as the
|
||||
* ability to automatically inject auth information where necessary.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type FetchApi = {
|
||||
fetch: typeof fetch;
|
||||
};
|
||||
|
||||
/**
|
||||
* A wrapper for the fetch API, that has additional behaviors such as the
|
||||
* ability to automatically inject auth information where necessary.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const fetchApiRef: ApiRef<FetchApi> = createApiRef({
|
||||
id: 'core.fetch',
|
||||
});
|
||||
@@ -29,6 +29,7 @@ export * from './ConfigApi';
|
||||
export * from './DiscoveryApi';
|
||||
export * from './ErrorApi';
|
||||
export * from './FeatureFlagsApi';
|
||||
export * from './FetchApi';
|
||||
export * from './IdentityApi';
|
||||
export * from './OAuthRequestApi';
|
||||
export * from './StorageApi';
|
||||
|
||||
Reference in New Issue
Block a user