Merge branch 'backstage:master' into master
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-auth-node': patch
|
||||
---
|
||||
|
||||
Added `AuthResolverContext.resolveOwnershipEntityRefs` as a way of accessing the default ownership resolution logic in sign-in resolvers, replacing `getDefaultOwnershipEntityRefs` from `@backstage/plugin-auth-backend`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/canon': patch
|
||||
---
|
||||
|
||||
To avoid conflicts with Backstage, we removed global styles and set font-family and font-weight for each components.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/plugin-proxy-backend': minor
|
||||
---
|
||||
|
||||
**BREAKING**: Removed support for the old backend system.
|
||||
|
||||
As part of this change the plugin export from `/alpha` as been removed. If you are currently importing `@backstage/plugin-proxy-backend/alpha`, please update your import to `@backstage/plugin-proxy-backend`.
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
'@backstage/plugin-auth-backend': patch
|
||||
---
|
||||
|
||||
Marked the remaining exports related to `createRouter` and the old backend system as deprecated.
|
||||
|
||||
For more information about migrating to the new backend system, see the [migration guide](https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin).
|
||||
|
||||
Support for the old backend system will be removed in the next release of this plugin.
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
'@backstage/plugin-auth-backend': patch
|
||||
---
|
||||
|
||||
Deprecated `getDefaultOwnershipEntityRefs` in favor of the new `.resolveOwnershipEntityRefs(...)` method in the `AuthResolverContext`.
|
||||
|
||||
The following code in a custom sign-in resolver:
|
||||
|
||||
```ts
|
||||
import { getDefaultOwnershipEntityRefs } from '@backstage/plugin-auth-backend';
|
||||
|
||||
// ...
|
||||
|
||||
const ent = getDefaultOwnershipEntityRefs(entity);
|
||||
```
|
||||
|
||||
Can be replaced with the following:
|
||||
|
||||
```ts
|
||||
const { ownershipEntityRefs: ent } = await ctx.resolveOwnershipEntityRefs(
|
||||
entity,
|
||||
);
|
||||
```
|
||||
@@ -298,10 +298,9 @@ of lower-level calls:
|
||||
|
||||
```ts
|
||||
// File: packages/backend/src/plugins/auth.ts
|
||||
import { getDefaultOwnershipEntityRefs } from '@backstage/plugin-auth-backend';
|
||||
|
||||
// ...
|
||||
async signInResolver({ profile: { email} }, ctx) {
|
||||
async signInResolver({ profile: { email } }, ctx) {
|
||||
if (!email) {
|
||||
throw new Error('User profile contained no email');
|
||||
}
|
||||
@@ -323,19 +322,19 @@ async signInResolver({ profile: { email} }, ctx) {
|
||||
//
|
||||
// You might also replace it if you for example want to filter out certain groups.
|
||||
//
|
||||
// Note that `getDefaultOwnershipEntityRefs` only includes groups to which the
|
||||
// user has a direct MEMBER_OF relationship. It's perfectly fine to include
|
||||
// groups that the user is transitively part of in the claims array, but the
|
||||
// catalog doesn't currently provide a direct way of accessing this list of
|
||||
// groups.
|
||||
const ownershipRefs = getDefaultOwnershipEntityRefs(entity);
|
||||
// Note that `ctx.resolveOwnershipEntityRefs(...)` by default only includes groups
|
||||
// to which the user has a direct MEMBER_OF relationship.
|
||||
// It's perfectly fine to include groups that the user is transitively part of
|
||||
// in the claims array, but the catalog doesn't currently provide a direct
|
||||
// way of accessing this list of groups.
|
||||
const { ownershipEntityRefs } = await ctx.resolveOwnershipEntityRefs(entity);
|
||||
|
||||
// The last step is to issue the token, where we might provide more options in the
|
||||
// future.
|
||||
return ctx.issueToken({
|
||||
claims: {
|
||||
sub: stringifyEntityRef(entity),
|
||||
ent: ownershipRefs,
|
||||
ent: ownershipEntityRefs,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -35,6 +35,10 @@ backend.start();
|
||||
|
||||
### Configuring the Catalog Collator
|
||||
|
||||
The following sections outlines the available configurations for this collator.
|
||||
|
||||
#### Scheduling
|
||||
|
||||
The default schedule for the Catalog Collator is to run every 10 minutes, you can provide your own schedule by adding it to your config:
|
||||
|
||||
```yaml title="app-config.yaml
|
||||
@@ -50,6 +54,42 @@ search:
|
||||
timeout: { minutes: 3 }
|
||||
```
|
||||
|
||||
#### Filtering
|
||||
|
||||
You may wish to collate specific subsets of entities in your Catalog, this can be accomplished using the `filter` configuration option. Here's a basic example:
|
||||
|
||||
```yaml title"app-config.yaml"
|
||||
search:
|
||||
collators:
|
||||
catalog:
|
||||
filter:
|
||||
kind: ['component', 'api']
|
||||
spec.lifecycle: production
|
||||
```
|
||||
|
||||
The above example will only collate entities that are `kind` equal to `component` or `api` AND have a `spec.lifecycle` set to `production`
|
||||
|
||||
You can also apply a more advanced filter like this:
|
||||
|
||||
```yaml title"app-config.yaml"
|
||||
search:
|
||||
collators:
|
||||
catalog:
|
||||
filter:
|
||||
- kind: ['API']
|
||||
spec.type: openapi
|
||||
- kind: ['Component']
|
||||
spec.lifecycle: experimental
|
||||
```
|
||||
|
||||
Now with this example it will collate all entities that are `kind` equal to `api` with a `spec.type` equal to `openapi` OR all entities that are `kind` equal to `component` AND have a `spec.lifecycle` set to `experimental`
|
||||
|
||||
:::tip
|
||||
|
||||
The filter configuration is implemented using the `EntityFilterQuery` syntax. The [reference documentation on `EntityFilterQuery`](https://backstage.io/docs/reference/catalog-client.entityfilterquery/) provides more details.
|
||||
|
||||
:::
|
||||
|
||||
## TechDocs
|
||||
|
||||
The TechDocs collator will index all the TechDocs in your Catalog. It is installed by default but if you need to add it manually here's how.
|
||||
|
||||
@@ -111,40 +111,6 @@ indexBuilder.addCollator({
|
||||
});
|
||||
```
|
||||
|
||||
## How to limit what can be searched in the Software Catalog
|
||||
|
||||
The Software Catalog includes a wealth of information about the components,
|
||||
systems, groups, users, and other aspects of your software ecosystem. However,
|
||||
you may not always want _every_ aspect to appear when a user searches the
|
||||
catalog. Examples include:
|
||||
|
||||
- Entities of kind `Location`, which are often not useful to Backstage users.
|
||||
- Entities of kind `User` or `Group`, if you'd prefer that users and groups be
|
||||
exposed to search in a different way (or not at all).
|
||||
|
||||
It's possible to write your own [Collator](./concepts.md#collators) to control
|
||||
exactly what's available to search, (or a [Decorator](./concepts.md#decorators)
|
||||
to filter things out here and there), but the `DefaultCatalogCollator` that's
|
||||
provided by `@backstage/plugin-catalog-backend` offers some configuration too!
|
||||
|
||||
```ts title="packages/backend/src/plugins/search.ts"
|
||||
indexBuilder.addCollator({
|
||||
defaultRefreshIntervalSeconds: 600,
|
||||
collator: DefaultCatalogCollator.fromConfig(env.config, {
|
||||
discovery: env.discovery,
|
||||
tokenManager: env.tokenManager,
|
||||
/* highlight-add-start */
|
||||
filter: {
|
||||
kind: ['API', 'Component', 'Domain', 'Group', 'System', 'User'],
|
||||
},
|
||||
/* highlight-add-end */
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
As shown above, you can add a catalog entity filter to narrow down what catalog
|
||||
entities are indexed by the search engine.
|
||||
|
||||
## How to customize search results highlighting styling
|
||||
|
||||
The default highlighting styling for matched terms in search results is your
|
||||
|
||||
@@ -48,7 +48,6 @@
|
||||
"@backstage/plugin-permission-backend": "workspace:^",
|
||||
"@backstage/plugin-permission-common": "workspace:^",
|
||||
"@backstage/plugin-permission-node": "workspace:^",
|
||||
"@backstage/plugin-proxy-backend": "workspace:^",
|
||||
"@backstage/plugin-scaffolder-backend": "workspace:^",
|
||||
"@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "workspace:^",
|
||||
"@backstage/plugin-scaffolder-backend-module-gitlab": "workspace:^",
|
||||
|
||||
@@ -42,7 +42,6 @@ import catalog from './plugins/catalog';
|
||||
import events from './plugins/events';
|
||||
import kubernetes from './plugins/kubernetes';
|
||||
import scaffolder from './plugins/scaffolder';
|
||||
import proxy from './plugins/proxy';
|
||||
import search from './plugins/search';
|
||||
import techdocs from './plugins/techdocs';
|
||||
import app from './plugins/app';
|
||||
@@ -129,7 +128,6 @@ async function main() {
|
||||
const catalogEnv = useHotMemoize(module, () => createEnv('catalog'));
|
||||
const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder'));
|
||||
const authEnv = useHotMemoize(module, () => createEnv('auth'));
|
||||
const proxyEnv = useHotMemoize(module, () => createEnv('proxy'));
|
||||
const searchEnv = useHotMemoize(module, () => createEnv('search'));
|
||||
const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs'));
|
||||
const kubernetesEnv = useHotMemoize(module, () => createEnv('kubernetes'));
|
||||
@@ -145,7 +143,6 @@ async function main() {
|
||||
apiRouter.use('/search', await search(searchEnv));
|
||||
apiRouter.use('/techdocs', await techdocs(techdocsEnv));
|
||||
apiRouter.use('/kubernetes', await kubernetes(kubernetesEnv));
|
||||
apiRouter.use('/proxy', await proxy(proxyEnv));
|
||||
apiRouter.use('/permission', await permission(permissionEnv));
|
||||
apiRouter.use(notFoundHandler());
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 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 { createRouter } from '@backstage/plugin-proxy-backend';
|
||||
import { Router } from 'express';
|
||||
import { PluginEnvironment } from '../types';
|
||||
|
||||
export default async function createPlugin(
|
||||
env: PluginEnvironment,
|
||||
): Promise<Router> {
|
||||
return await createRouter({
|
||||
logger: env.logger,
|
||||
config: env.config,
|
||||
discovery: env.discovery,
|
||||
});
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
.canon-Box {
|
||||
font-family: var(--canon-font-regular);
|
||||
font-weight: var(--canon-font-weight-regular);
|
||||
color: var(--canon-fg-primary);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
gap: var(--canon-space-2);
|
||||
font-size: var(--canon-font-size-xs);
|
||||
font-family: var(--canon-font-regular);
|
||||
font-weight: var(--canon-font-weight-regular);
|
||||
color: var(--canon-fg-primary);
|
||||
user-select: none;
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
padding: 0 var(--canon-space-4);
|
||||
background-color: var(--canon-bg-elevated);
|
||||
font-size: var(--canon-font-size-3);
|
||||
font-family: var(--canon-font-regular);
|
||||
font-weight: var(--canon-font-weight-regular);
|
||||
color: var(--canon-fg-primary);
|
||||
transition: border-color 0.2s ease-in-out, outline-color 0.2s ease-in-out;
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
padding-bottom: var(--canon-space-0_5);
|
||||
padding-top: var(--canon-space-0_5);
|
||||
font-size: var(--canon-font-size-3);
|
||||
font-family: var(--canon-font-regular);
|
||||
font-weight: var(--canon-font-weight-regular);
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 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 './normalize.css';
|
||||
|
||||
@layer base {
|
||||
*,
|
||||
html,
|
||||
body {
|
||||
font-family: var(--canon-font-regular);
|
||||
font-weight: var(--canon-font-weight-regular);
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
/* Normalize */
|
||||
@import './base.css';
|
||||
@import './normalize.css';
|
||||
@import './utilities.css';
|
||||
|
||||
/* Light theme tokens */
|
||||
|
||||
@@ -235,6 +235,7 @@ describe('vmwareCloudAuthenticator', () => {
|
||||
signInWithCatalogUser: jest.fn().mockResolvedValue({
|
||||
token: 'backstageToken',
|
||||
}),
|
||||
resolveOwnershipEntityRefs: jest.fn(),
|
||||
};
|
||||
|
||||
oAuthState = {
|
||||
@@ -432,6 +433,7 @@ describe('vmwareCloudAuthenticator', () => {
|
||||
signInWithCatalogUser: jest.fn().mockResolvedValue({
|
||||
token: 'backstageToken',
|
||||
}),
|
||||
resolveOwnershipEntityRefs: jest.fn(),
|
||||
};
|
||||
|
||||
refreshRequest = {
|
||||
|
||||
@@ -121,7 +121,7 @@ export type BitbucketServerOAuthResult = {
|
||||
refreshToken?: string;
|
||||
};
|
||||
|
||||
// @public
|
||||
// @public @deprecated
|
||||
export class CatalogIdentityClient {
|
||||
constructor(options: {
|
||||
catalogApi: CatalogApi;
|
||||
@@ -176,7 +176,7 @@ export type CloudflareAccessResult = {
|
||||
// @public @deprecated (undocumented)
|
||||
export type CookieConfigurer = CookieConfigurer_2;
|
||||
|
||||
// @public
|
||||
// @public @deprecated
|
||||
export function createAuthProviderIntegration<
|
||||
TCreateOptions extends unknown[],
|
||||
TResolvers extends {
|
||||
@@ -190,13 +190,13 @@ export function createAuthProviderIntegration<
|
||||
resolvers: Readonly<string extends keyof TResolvers ? never : TResolvers>;
|
||||
}>;
|
||||
|
||||
// @public (undocumented)
|
||||
// @public @deprecated (undocumented)
|
||||
export function createOriginFilter(config: Config): (origin: string) => boolean;
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export function createRouter(options: RouterOptions): Promise<express.Router>;
|
||||
|
||||
// @public
|
||||
// @public @deprecated
|
||||
export const defaultAuthProviderFactories: {
|
||||
[providerId: string]: AuthProviderFactory_2;
|
||||
};
|
||||
@@ -216,10 +216,10 @@ export type GcpIapResult = GcpIapResult_2;
|
||||
// @public @deprecated
|
||||
export type GcpIapTokenInfo = GcpIapTokenInfo_2;
|
||||
|
||||
// @public
|
||||
// @public @deprecated
|
||||
export function getDefaultOwnershipEntityRefs(entity: Entity): string[];
|
||||
|
||||
// @public (undocumented)
|
||||
// @public @deprecated (undocumented)
|
||||
export type GithubOAuthResult = {
|
||||
fullProfile: Profile;
|
||||
params: {
|
||||
@@ -361,12 +361,12 @@ export const prepareBackstageIdentityResponse: typeof prepareBackstageIdentityRe
|
||||
// @public @deprecated (undocumented)
|
||||
export type ProfileInfo = ProfileInfo_2;
|
||||
|
||||
// @public (undocumented)
|
||||
// @public @deprecated (undocumented)
|
||||
export type ProviderFactories = {
|
||||
[s: string]: AuthProviderFactory_2;
|
||||
};
|
||||
|
||||
// @public
|
||||
// @public @deprecated
|
||||
export const providers: Readonly<{
|
||||
atlassian: Readonly<{
|
||||
create: (
|
||||
@@ -676,7 +676,7 @@ export interface RouterOptions {
|
||||
tokenManager?: TokenManager;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
// @public @deprecated (undocumented)
|
||||
export type SamlAuthResult = {
|
||||
fullProfile: any;
|
||||
};
|
||||
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
* A catalog client tailored for reading out identity data from the catalog.
|
||||
*
|
||||
* @public
|
||||
* @deprecated Use the provided `AuthResolverContext` instead, see https://backstage.io/docs/auth/identity-resolver#building-custom-resolvers
|
||||
*/
|
||||
export class CatalogIdentityClient {
|
||||
private readonly catalogApi: CatalogApi;
|
||||
|
||||
@@ -46,6 +46,7 @@ import { CatalogIdentityClient } from '../catalog';
|
||||
* A reference to the entity itself will also be included in the returned array.
|
||||
*
|
||||
* @public
|
||||
* @deprecated use `ctx.resolveOwnershipEntityRefs(entity)` from the provided `AuthResolverContext` instead.
|
||||
*/
|
||||
export function getDefaultOwnershipEntityRefs(entity: Entity) {
|
||||
const membershipRefs =
|
||||
@@ -164,21 +165,26 @@ export class CatalogAuthResolverContext implements AuthResolverContext {
|
||||
|
||||
async signInWithCatalogUser(query: AuthResolverCatalogUserQuery) {
|
||||
const { entity } = await this.findCatalogUser(query);
|
||||
let ent: string[];
|
||||
if (this.ownershipResolver) {
|
||||
const { ownershipEntityRefs } =
|
||||
await this.ownershipResolver.resolveOwnershipEntityRefs(entity);
|
||||
ent = ownershipEntityRefs;
|
||||
} else {
|
||||
ent = getDefaultOwnershipEntityRefs(entity);
|
||||
}
|
||||
|
||||
const { ownershipEntityRefs } = await this.resolveOwnershipEntityRefs(
|
||||
entity,
|
||||
);
|
||||
|
||||
const token = await this.tokenIssuer.issueToken({
|
||||
claims: {
|
||||
sub: stringifyEntityRef(entity),
|
||||
ent,
|
||||
ent: ownershipEntityRefs,
|
||||
},
|
||||
});
|
||||
return { token };
|
||||
}
|
||||
|
||||
async resolveOwnershipEntityRefs(
|
||||
entity: Entity,
|
||||
): Promise<{ ownershipEntityRefs: string[] }> {
|
||||
if (this.ownershipResolver) {
|
||||
return this.ownershipResolver.resolveOwnershipEntityRefs(entity);
|
||||
}
|
||||
return { ownershipEntityRefs: getDefaultOwnershipEntityRefs(entity) };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import { AuthHandler } from '../types';
|
||||
* Auth provider integration for Atlassian auth
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export const atlassian = createAuthProviderIntegration({
|
||||
create(options?: {
|
||||
|
||||
@@ -47,6 +47,7 @@ export type Auth0AuthProviderOptions = OAuthProviderOptions & {
|
||||
* Auth provider integration for auth0 auth
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export const auth0 = createAuthProviderIntegration({
|
||||
create(options?: {
|
||||
|
||||
@@ -29,6 +29,7 @@ import { createAuthProviderIntegration } from '../createAuthProviderIntegration'
|
||||
* Auth provider integration for AWS ALB auth
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export const awsAlb = createAuthProviderIntegration({
|
||||
create(options?: {
|
||||
|
||||
@@ -31,6 +31,7 @@ export type EasyAuthResult = AzureEasyAuthResult;
|
||||
* Auth provider integration for Azure EasyAuth
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export const easyAuth = createAuthProviderIntegration({
|
||||
create(options?: {
|
||||
|
||||
@@ -69,6 +69,7 @@ export type BitbucketPassportProfile = PassportProfile & {
|
||||
* Auth provider integration for Bitbucket auth
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export const bitbucket = createAuthProviderIntegration({
|
||||
create(options?: {
|
||||
|
||||
@@ -125,6 +125,7 @@ export type CloudflareAccessResult = {
|
||||
* Auth provider integration for Cloudflare Access auth
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export const cfAccess = createAuthProviderIntegration({
|
||||
create(options: {
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
* supplies built-in sign-in resolvers for the specific provider.
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export function createAuthProviderIntegration<
|
||||
TCreateOptions extends unknown[],
|
||||
|
||||
@@ -27,6 +27,7 @@ import { GcpIapResult } from './types';
|
||||
* Auth provider integration for Google Identity-Aware Proxy auth
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export const gcpIap = createAuthProviderIntegration({
|
||||
create(options: {
|
||||
|
||||
@@ -25,7 +25,10 @@ import {
|
||||
} from '@backstage/plugin-auth-node';
|
||||
import { githubAuthenticator } from '@backstage/plugin-auth-backend-module-github-provider';
|
||||
|
||||
/** @public */
|
||||
/**
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export type GithubOAuthResult = {
|
||||
fullProfile: PassportProfile;
|
||||
params: {
|
||||
@@ -41,6 +44,7 @@ export type GithubOAuthResult = {
|
||||
* Auth provider integration for GitHub auth
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export const github = createAuthProviderIntegration({
|
||||
create(options?: {
|
||||
|
||||
@@ -31,6 +31,7 @@ import { gitlabAuthenticator } from '@backstage/plugin-auth-backend-module-gitla
|
||||
* Auth provider integration for GitLab auth
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export const gitlab = createAuthProviderIntegration({
|
||||
create(options?: {
|
||||
|
||||
@@ -36,6 +36,7 @@ import { AuthHandler } from '../types';
|
||||
* Auth provider integration for Google auth
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export const google = createAuthProviderIntegration({
|
||||
create(options?: {
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
* Auth provider integration for Microsoft auth
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export const microsoft = createAuthProviderIntegration({
|
||||
create(options?: {
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
* Auth provider integration for oauth2-proxy auth
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export const oauth2Proxy = createAuthProviderIntegration({
|
||||
create(options: {
|
||||
|
||||
@@ -31,6 +31,7 @@ import { oauth2Authenticator } from '@backstage/plugin-auth-backend-module-oauth
|
||||
* Auth provider integration for generic OAuth2 auth
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export const oauth2 = createAuthProviderIntegration({
|
||||
create(options?: {
|
||||
|
||||
@@ -119,6 +119,7 @@ describe('oidc.create', () => {
|
||||
issueToken: jest.fn(),
|
||||
findCatalogUser: jest.fn(),
|
||||
signInWithCatalogUser: jest.fn(),
|
||||
resolveOwnershipEntityRefs: jest.fn(),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
* Auth provider integration for generic OpenID Connect auth
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export const oidc = createAuthProviderIntegration({
|
||||
create(options?: {
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
* Auth provider integration for Okta auth
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export const okta = createAuthProviderIntegration({
|
||||
create(options?: {
|
||||
|
||||
@@ -31,6 +31,7 @@ import { AuthHandler } from '../types';
|
||||
* Auth provider integration for OneLogin auth
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export const onelogin = createAuthProviderIntegration({
|
||||
create(options?: {
|
||||
|
||||
@@ -38,6 +38,7 @@ import { AuthProviderFactory } from '@backstage/plugin-auth-node';
|
||||
* All built-in auth provider integrations.
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export const providers = Object.freeze({
|
||||
atlassian,
|
||||
@@ -64,6 +65,7 @@ export const providers = Object.freeze({
|
||||
* All auth provider factories that are installed by default.
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export const defaultAuthProviderFactories: {
|
||||
[providerId: string]: AuthProviderFactory;
|
||||
|
||||
@@ -34,7 +34,10 @@ import { Minimatch } from 'minimatch';
|
||||
import { CatalogAuthResolverContext } from '../lib/resolvers/CatalogAuthResolverContext';
|
||||
import { TokenIssuer } from '../identity/types';
|
||||
|
||||
/** @public */
|
||||
/**
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export type ProviderFactories = { [s: string]: AuthProviderFactory };
|
||||
|
||||
export function bindProviderRouters(
|
||||
@@ -145,7 +148,10 @@ export function bindProviderRouters(
|
||||
}
|
||||
}
|
||||
|
||||
/** @public */
|
||||
/**
|
||||
* @public
|
||||
* @deprecated this export will be removed
|
||||
*/
|
||||
export function createOriginFilter(
|
||||
config: Config,
|
||||
): (origin: string) => boolean {
|
||||
|
||||
@@ -37,7 +37,10 @@ import {
|
||||
SignInResolver,
|
||||
} from '@backstage/plugin-auth-node';
|
||||
|
||||
/** @public */
|
||||
/**
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export type SamlAuthResult = {
|
||||
fullProfile: any;
|
||||
};
|
||||
|
||||
@@ -113,6 +113,9 @@ export type AuthResolverContext = {
|
||||
signInWithCatalogUser(
|
||||
query: AuthResolverCatalogUserQuery,
|
||||
): Promise<BackstageSignInResult>;
|
||||
resolveOwnershipEntityRefs(entity: Entity): Promise<{
|
||||
ownershipEntityRefs: string[];
|
||||
}>;
|
||||
};
|
||||
|
||||
// @public
|
||||
|
||||
@@ -161,6 +161,14 @@ export type AuthResolverContext = {
|
||||
signInWithCatalogUser(
|
||||
query: AuthResolverCatalogUserQuery,
|
||||
): Promise<BackstageSignInResult>;
|
||||
|
||||
/**
|
||||
* Resolves the ownership entity references for the provided entity.
|
||||
* This will use the `AuthOwnershipResolver` if one is installed, and otherwise fall back to the default resolution logic.
|
||||
*/
|
||||
resolveOwnershipEntityRefs(
|
||||
entity: Entity,
|
||||
): Promise<{ ownershipEntityRefs: string[] }>;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,7 +19,7 @@ import { createBackendModule } from '@backstage/backend-plugin-api';
|
||||
import { proxyEndpointsExtensionPoint } from '@backstage/plugin-proxy-node/alpha';
|
||||
|
||||
const backend = createBackend();
|
||||
backend.add(import('../src/alpha'));
|
||||
backend.add(import('../src'));
|
||||
|
||||
backend.add(
|
||||
createBackendModule({
|
||||
|
||||
@@ -24,16 +24,12 @@
|
||||
"license": "Apache-2.0",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./alpha": "./src/alpha.ts",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"alpha": [
|
||||
"src/alpha.ts"
|
||||
],
|
||||
"package.json": [
|
||||
"package.json"
|
||||
]
|
||||
@@ -53,21 +49,11 @@
|
||||
"test": "backstage-cli package test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.25.0",
|
||||
"@backstage/backend-plugin-api": "workspace:^",
|
||||
"@backstage/config": "workspace:^",
|
||||
"@backstage/plugin-proxy-node": "workspace:^",
|
||||
"@backstage/types": "workspace:^",
|
||||
"@types/express": "^4.17.6",
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^4.1.0",
|
||||
"http-proxy-middleware": "^2.0.0",
|
||||
"morgan": "^1.10.0",
|
||||
"uuid": "^11.0.0",
|
||||
"winston": "^3.2.1",
|
||||
"yaml": "^2.0.0",
|
||||
"yn": "^4.0.0",
|
||||
"yup": "^1.0.0"
|
||||
"http-proxy-middleware": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/backend-app-api": "workspace:^",
|
||||
@@ -76,8 +62,9 @@
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@backstage/config-loader": "workspace:^",
|
||||
"@backstage/errors": "workspace:^",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/http-proxy-middleware": "^1.0.0",
|
||||
"@types/yup": "^0.32.0",
|
||||
"express": "^4.17.1",
|
||||
"msw": "^2.0.0"
|
||||
},
|
||||
"configSchema": "config.d.ts"
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
## API Report File for "@backstage/plugin-proxy-backend"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { BackendFeature } from '@backstage/backend-plugin-api';
|
||||
|
||||
// @alpha (undocumented)
|
||||
const _feature: BackendFeature;
|
||||
export default _feature;
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
@@ -4,32 +4,8 @@
|
||||
|
||||
```ts
|
||||
import { BackendFeature } from '@backstage/backend-plugin-api';
|
||||
import { DiscoveryService } from '@backstage/backend-plugin-api';
|
||||
import express from 'express';
|
||||
import { Logger } from 'winston';
|
||||
import { ProxyConfig } from '@backstage/plugin-proxy-node/alpha';
|
||||
import { RootConfigService } from '@backstage/backend-plugin-api';
|
||||
|
||||
// @public @deprecated
|
||||
export function createRouter(options: RouterOptions): Promise<express.Router>;
|
||||
|
||||
// @public
|
||||
const proxyPlugin: BackendFeature;
|
||||
export default proxyPlugin;
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export interface RouterOptions {
|
||||
// (undocumented)
|
||||
additionalEndpoints?: ProxyConfig;
|
||||
// (undocumented)
|
||||
config: RootConfigService;
|
||||
// (undocumented)
|
||||
discovery: DiscoveryService;
|
||||
// (undocumented)
|
||||
logger: Logger;
|
||||
// (undocumented)
|
||||
reviveConsumedRequestBodies?: boolean;
|
||||
// (undocumented)
|
||||
skipInvalidProxies?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 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 { proxyPlugin } from './plugin';
|
||||
|
||||
/** @alpha */
|
||||
const _feature = proxyPlugin;
|
||||
export default _feature;
|
||||
@@ -21,4 +21,3 @@
|
||||
*/
|
||||
|
||||
export { proxyPlugin as default } from './plugin';
|
||||
export * from './service';
|
||||
|
||||
@@ -14,12 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { loggerToWinstonLogger } from '@backstage/backend-common';
|
||||
import {
|
||||
createBackendPlugin,
|
||||
coreServices,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { createRouterInternal } from './service/router';
|
||||
import { createRouter } from './service/router';
|
||||
import { proxyEndpointsExtensionPoint } from '@backstage/plugin-proxy-node/alpha';
|
||||
|
||||
/**
|
||||
@@ -45,10 +44,10 @@ export const proxyPlugin = createBackendPlugin({
|
||||
httpRouter: coreServices.httpRouter,
|
||||
},
|
||||
async init({ config, discovery, logger, httpRouter }) {
|
||||
await createRouterInternal({
|
||||
await createRouter({
|
||||
config,
|
||||
discovery,
|
||||
logger: loggerToWinstonLogger(logger),
|
||||
logger,
|
||||
httpRouterService: httpRouter,
|
||||
additionalEndpoints,
|
||||
});
|
||||
|
||||
@@ -60,7 +60,7 @@ describe('createRouter reloadable configuration', () => {
|
||||
|
||||
const backend = await startTestBackend({
|
||||
features: [
|
||||
import('../alpha'),
|
||||
import('..'),
|
||||
createServiceFactory({
|
||||
service: coreServices.rootConfig,
|
||||
deps: {},
|
||||
|
||||
@@ -77,7 +77,7 @@ describe('credentials', () => {
|
||||
|
||||
const backend = await startTestBackend({
|
||||
features: [
|
||||
import('../alpha'),
|
||||
import('..'),
|
||||
mockServices.rootConfig.factory({ data: config }),
|
||||
authServiceFactory,
|
||||
httpAuthServiceFactory,
|
||||
|
||||
@@ -14,12 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
HostDiscovery,
|
||||
loggerToWinstonLogger,
|
||||
} from '@backstage/backend-common';
|
||||
import { mockServices } from '@backstage/backend-test-utils';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { Request, Response } from 'express';
|
||||
import * as http from 'http';
|
||||
import {
|
||||
@@ -39,27 +34,37 @@ const mockCreateProxyMiddleware = createProxyMiddleware as jest.MockedFunction<
|
||||
>;
|
||||
|
||||
describe('createRouter', () => {
|
||||
const deps = {
|
||||
logger: mockServices.logger.mock(),
|
||||
discovery: mockServices.discovery(),
|
||||
httpRouterService: mockServices.httpRouter.mock(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('where all proxy config are valid', () => {
|
||||
const logger = loggerToWinstonLogger(mockServices.logger.mock());
|
||||
const config = new ConfigReader({
|
||||
backend: {
|
||||
baseUrl: 'https://example.com:7007',
|
||||
listen: {
|
||||
port: 7007,
|
||||
const config = mockServices.rootConfig({
|
||||
data: {
|
||||
backend: {
|
||||
baseUrl: 'https://example.com:7007',
|
||||
listen: {
|
||||
port: 7007,
|
||||
},
|
||||
},
|
||||
},
|
||||
proxy: {
|
||||
endpoints: {
|
||||
'/test': {
|
||||
target: 'https://example.com',
|
||||
headers: {
|
||||
Authorization: 'Bearer supersecret',
|
||||
proxy: {
|
||||
endpoints: {
|
||||
'/test': {
|
||||
target: 'https://example.com',
|
||||
headers: {
|
||||
Authorization: 'Bearer supersecret',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const discovery = HostDiscovery.fromConfig(config);
|
||||
|
||||
beforeEach(() => {
|
||||
mockCreateProxyMiddleware.mockClear();
|
||||
@@ -67,29 +72,29 @@ describe('createRouter', () => {
|
||||
|
||||
it('works', async () => {
|
||||
const router = await createRouter({
|
||||
...deps,
|
||||
config,
|
||||
logger,
|
||||
discovery,
|
||||
});
|
||||
expect(router).toBeDefined();
|
||||
});
|
||||
|
||||
it('supports deprecated proxy configuration', async () => {
|
||||
const router = await createRouter({
|
||||
...deps,
|
||||
config: mockServices.rootConfig({
|
||||
data: {
|
||||
proxy: {
|
||||
'/test': {
|
||||
target: 'https://example.com',
|
||||
headers: {
|
||||
Authorization: 'Bearer supersecret',
|
||||
endpoints: {
|
||||
'/test': {
|
||||
target: 'https://example.com',
|
||||
headers: {
|
||||
Authorization: 'Bearer supersecret',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
logger,
|
||||
discovery,
|
||||
});
|
||||
expect(router).toBeDefined();
|
||||
expect(mockCreateProxyMiddleware).toHaveBeenCalledWith(
|
||||
@@ -102,10 +107,22 @@ describe('createRouter', () => {
|
||||
|
||||
it('revives request bodies when set', async () => {
|
||||
const router = await createRouter({
|
||||
config,
|
||||
logger,
|
||||
discovery,
|
||||
reviveConsumedRequestBodies: true,
|
||||
...deps,
|
||||
config: mockServices.rootConfig({
|
||||
data: {
|
||||
proxy: {
|
||||
endpoints: {
|
||||
'/test': {
|
||||
target: 'https://example.com',
|
||||
headers: {
|
||||
Authorization: 'Bearer supersecret',
|
||||
},
|
||||
},
|
||||
},
|
||||
reviveConsumedRequestBodies: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
expect(router).toBeDefined();
|
||||
|
||||
@@ -120,8 +137,7 @@ describe('createRouter', () => {
|
||||
it('does not revive request bodies when not set', async () => {
|
||||
const router = await createRouter({
|
||||
config,
|
||||
logger,
|
||||
discovery,
|
||||
...deps,
|
||||
});
|
||||
expect(router).toBeDefined();
|
||||
|
||||
@@ -133,32 +149,30 @@ describe('createRouter', () => {
|
||||
|
||||
describe('where buildMiddleware would fail', () => {
|
||||
it('throws an error if skip failures is not set', async () => {
|
||||
const logger = loggerToWinstonLogger(mockServices.logger.mock());
|
||||
logger.warn = jest.fn();
|
||||
const config = new ConfigReader({
|
||||
backend: {
|
||||
baseUrl: 'https://example.com:7007',
|
||||
listen: {
|
||||
port: 7007,
|
||||
const config = mockServices.rootConfig({
|
||||
data: {
|
||||
backend: {
|
||||
baseUrl: 'https://example.com:7007',
|
||||
listen: {
|
||||
port: 7007,
|
||||
},
|
||||
},
|
||||
},
|
||||
// no target would cause the buildMiddleware to fail
|
||||
proxy: {
|
||||
endpoints: {
|
||||
'/test': {
|
||||
headers: {
|
||||
Authorization: 'Bearer supersecret',
|
||||
// no target would cause the buildMiddleware to fail
|
||||
proxy: {
|
||||
endpoints: {
|
||||
'/test': {
|
||||
headers: {
|
||||
Authorization: 'Bearer supersecret',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const discovery = HostDiscovery.fromConfig(config);
|
||||
await expect(
|
||||
createRouter({
|
||||
...deps,
|
||||
config,
|
||||
logger,
|
||||
discovery,
|
||||
}),
|
||||
).rejects.toThrow(
|
||||
new Error(
|
||||
@@ -168,34 +182,32 @@ describe('createRouter', () => {
|
||||
});
|
||||
|
||||
it('works if skip failures is set', async () => {
|
||||
const logger = loggerToWinstonLogger(mockServices.logger.mock());
|
||||
logger.warn = jest.fn();
|
||||
const config = new ConfigReader({
|
||||
backend: {
|
||||
baseUrl: 'https://example.com:7007',
|
||||
listen: {
|
||||
port: 7007,
|
||||
},
|
||||
},
|
||||
// no target would cause the buildMiddleware to fail
|
||||
proxy: {
|
||||
endpoints: {
|
||||
'/test': {
|
||||
headers: {
|
||||
Authorization: 'Bearer supersecret',
|
||||
},
|
||||
const config = mockServices.rootConfig({
|
||||
data: {
|
||||
backend: {
|
||||
baseUrl: 'https://example.com:7007',
|
||||
listen: {
|
||||
port: 7007,
|
||||
},
|
||||
},
|
||||
// no target would cause the buildMiddleware to fail
|
||||
proxy: {
|
||||
endpoints: {
|
||||
'/test': {
|
||||
headers: {
|
||||
Authorization: 'Bearer supersecret',
|
||||
},
|
||||
},
|
||||
},
|
||||
skipInvalidProxies: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
const discovery = HostDiscovery.fromConfig(config);
|
||||
const router = await createRouter({
|
||||
...deps,
|
||||
config,
|
||||
logger,
|
||||
discovery,
|
||||
skipInvalidProxies: true,
|
||||
});
|
||||
expect((logger.warn as jest.Mock).mock.calls[0][0]).toEqual(
|
||||
expect(deps.logger.warn.mock.calls[0][0]).toEqual(
|
||||
'skipped configuring /test due to Proxy target for route "/test" must be a string, but is of type undefined',
|
||||
);
|
||||
expect(router).toBeDefined();
|
||||
@@ -204,14 +216,21 @@ describe('createRouter', () => {
|
||||
});
|
||||
|
||||
describe('buildMiddleware', () => {
|
||||
const logger = loggerToWinstonLogger(mockServices.logger.mock());
|
||||
const logger = mockServices.logger.mock();
|
||||
const httpRouterService = mockServices.httpRouter.mock();
|
||||
|
||||
beforeEach(() => {
|
||||
mockCreateProxyMiddleware.mockClear();
|
||||
});
|
||||
|
||||
it('accepts strings prefixed by /', async () => {
|
||||
buildMiddleware('/proxy', logger, '/test', 'http://mocked');
|
||||
buildMiddleware(
|
||||
'/proxy',
|
||||
logger,
|
||||
'/test',
|
||||
'http://mocked',
|
||||
httpRouterService,
|
||||
);
|
||||
|
||||
expect(createProxyMiddleware).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -227,11 +246,20 @@ describe('buildMiddleware', () => {
|
||||
|
||||
expect(fullConfig.pathRewrite).toEqual({ '^/proxy/test/?': '/' });
|
||||
expect(fullConfig.changeOrigin).toBe(true);
|
||||
expect(fullConfig.logProvider!(logger)).toBe(logger);
|
||||
|
||||
expect(logger.info).not.toHaveBeenCalled();
|
||||
fullConfig.logProvider!({} as any).log('test');
|
||||
expect(logger.info).toHaveBeenCalledWith('test');
|
||||
});
|
||||
|
||||
it('accepts routes not prefixed with / when path is not suffixed with /', async () => {
|
||||
buildMiddleware('/proxy', logger, 'test', 'http://mocked');
|
||||
buildMiddleware(
|
||||
'/proxy',
|
||||
logger,
|
||||
'test',
|
||||
'http://mocked',
|
||||
httpRouterService,
|
||||
);
|
||||
|
||||
expect(createProxyMiddleware).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -247,11 +275,16 @@ describe('buildMiddleware', () => {
|
||||
|
||||
expect(fullConfig.pathRewrite).toEqual({ '^/proxy/test/?': '/' });
|
||||
expect(fullConfig.changeOrigin).toBe(true);
|
||||
expect(fullConfig.logProvider!(logger)).toBe(logger);
|
||||
});
|
||||
|
||||
it('accepts routes prefixed with / when path is suffixed with /', async () => {
|
||||
buildMiddleware('/proxy/', logger, '/test', 'http://mocked');
|
||||
buildMiddleware(
|
||||
'/proxy/',
|
||||
logger,
|
||||
'/test',
|
||||
'http://mocked',
|
||||
httpRouterService,
|
||||
);
|
||||
|
||||
expect(createProxyMiddleware).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -267,14 +300,19 @@ describe('buildMiddleware', () => {
|
||||
|
||||
expect(fullConfig.pathRewrite).toEqual({ '^/proxy/test/?': '/' });
|
||||
expect(fullConfig.changeOrigin).toBe(true);
|
||||
expect(fullConfig.logProvider!(logger)).toBe(logger);
|
||||
});
|
||||
|
||||
it('limits allowedMethods', async () => {
|
||||
buildMiddleware('/proxy', logger, '/test', {
|
||||
target: 'http://mocked',
|
||||
allowedMethods: ['GET', 'DELETE'],
|
||||
});
|
||||
buildMiddleware(
|
||||
'/proxy',
|
||||
logger,
|
||||
'/test',
|
||||
{
|
||||
target: 'http://mocked',
|
||||
allowedMethods: ['GET', 'DELETE'],
|
||||
},
|
||||
httpRouterService,
|
||||
);
|
||||
|
||||
expect(createProxyMiddleware).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -290,13 +328,18 @@ describe('buildMiddleware', () => {
|
||||
|
||||
expect(fullConfig.pathRewrite).toEqual({ '^/proxy/test/?': '/' });
|
||||
expect(fullConfig.changeOrigin).toBe(true);
|
||||
expect(fullConfig.logProvider!(logger)).toBe(logger);
|
||||
});
|
||||
|
||||
it('permits default headers', async () => {
|
||||
buildMiddleware('/proxy', logger, '/test', {
|
||||
target: 'http://mocked',
|
||||
});
|
||||
buildMiddleware(
|
||||
'/proxy',
|
||||
logger,
|
||||
'/test',
|
||||
{
|
||||
target: 'http://mocked',
|
||||
},
|
||||
httpRouterService,
|
||||
);
|
||||
|
||||
expect(createProxyMiddleware).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -334,12 +377,18 @@ describe('buildMiddleware', () => {
|
||||
});
|
||||
|
||||
it('permits default and configured headers', async () => {
|
||||
buildMiddleware('/proxy', logger, '/test', {
|
||||
target: 'http://mocked',
|
||||
headers: {
|
||||
Authorization: 'my-token',
|
||||
buildMiddleware(
|
||||
'/proxy',
|
||||
logger,
|
||||
'/test',
|
||||
{
|
||||
target: 'http://mocked',
|
||||
headers: {
|
||||
Authorization: 'my-token',
|
||||
},
|
||||
},
|
||||
});
|
||||
httpRouterService,
|
||||
);
|
||||
|
||||
expect(createProxyMiddleware).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -367,10 +416,16 @@ describe('buildMiddleware', () => {
|
||||
});
|
||||
|
||||
it('permits configured headers', async () => {
|
||||
buildMiddleware('/proxy', logger, '/test', {
|
||||
target: 'http://mocked',
|
||||
allowedHeaders: ['authorization', 'cookie'],
|
||||
});
|
||||
buildMiddleware(
|
||||
'/proxy',
|
||||
logger,
|
||||
'/test',
|
||||
{
|
||||
target: 'http://mocked',
|
||||
allowedHeaders: ['authorization', 'cookie'],
|
||||
},
|
||||
httpRouterService,
|
||||
);
|
||||
|
||||
expect(createProxyMiddleware).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -399,9 +454,15 @@ describe('buildMiddleware', () => {
|
||||
});
|
||||
|
||||
it('responds default headers', async () => {
|
||||
buildMiddleware('/proxy', logger, '/test', {
|
||||
target: 'http://mocked',
|
||||
});
|
||||
buildMiddleware(
|
||||
'/proxy',
|
||||
logger,
|
||||
'/test',
|
||||
{
|
||||
target: 'http://mocked',
|
||||
},
|
||||
httpRouterService,
|
||||
);
|
||||
|
||||
expect(createProxyMiddleware).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -441,10 +502,16 @@ describe('buildMiddleware', () => {
|
||||
});
|
||||
|
||||
it('responds configured headers', async () => {
|
||||
buildMiddleware('/proxy', logger, '/test', {
|
||||
target: 'http://mocked',
|
||||
allowedHeaders: ['set-cookie'],
|
||||
});
|
||||
buildMiddleware(
|
||||
'/proxy',
|
||||
logger,
|
||||
'/test',
|
||||
{
|
||||
target: 'http://mocked',
|
||||
allowedHeaders: ['set-cookie'],
|
||||
},
|
||||
httpRouterService,
|
||||
);
|
||||
|
||||
expect(createProxyMiddleware).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -477,6 +544,7 @@ describe('buildMiddleware', () => {
|
||||
{
|
||||
target: 'http://mocked',
|
||||
},
|
||||
httpRouterService,
|
||||
true,
|
||||
);
|
||||
|
||||
@@ -497,9 +565,15 @@ describe('buildMiddleware', () => {
|
||||
});
|
||||
|
||||
it('does not revive request body when not configured', async () => {
|
||||
buildMiddleware('/proxy', logger, '/test', {
|
||||
target: 'http://mocked',
|
||||
});
|
||||
buildMiddleware(
|
||||
'/proxy',
|
||||
logger,
|
||||
'/test',
|
||||
{
|
||||
target: 'http://mocked',
|
||||
},
|
||||
httpRouterService,
|
||||
);
|
||||
|
||||
expect(createProxyMiddleware).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -511,10 +585,22 @@ describe('buildMiddleware', () => {
|
||||
|
||||
it('rejects malformed target URLs', async () => {
|
||||
expect(() =>
|
||||
buildMiddleware('/proxy', logger, '/test', 'backstage.io'),
|
||||
buildMiddleware(
|
||||
'/proxy',
|
||||
logger,
|
||||
'/test',
|
||||
'backstage.io',
|
||||
httpRouterService,
|
||||
),
|
||||
).toThrow(/Proxy target is not a valid URL/);
|
||||
expect(() =>
|
||||
buildMiddleware('/proxy', logger, '/test', { target: 'backstage.io' }),
|
||||
buildMiddleware(
|
||||
'/proxy',
|
||||
logger,
|
||||
'/test',
|
||||
{ target: 'backstage.io' },
|
||||
httpRouterService,
|
||||
),
|
||||
).toThrow(/Proxy target is not a valid URL/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,20 +14,19 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Config } from '@backstage/config';
|
||||
import express from 'express';
|
||||
import type express from 'express';
|
||||
import Router from 'express-promise-router';
|
||||
import {
|
||||
createProxyMiddleware,
|
||||
fixRequestBody,
|
||||
RequestHandler,
|
||||
} from 'http-proxy-middleware';
|
||||
import { Logger } from 'winston';
|
||||
import http from 'http';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import {
|
||||
DiscoveryService,
|
||||
HttpRouterService,
|
||||
LoggerService,
|
||||
RootConfigService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { ProxyConfig } from '@backstage/plugin-proxy-node/alpha';
|
||||
@@ -54,15 +53,13 @@ const safeForwardHeaders = [
|
||||
];
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @deprecated Please migrate to the new backend system as this will be removed in the future.
|
||||
* @internal
|
||||
*/
|
||||
export interface RouterOptions {
|
||||
logger: Logger;
|
||||
logger: LoggerService;
|
||||
config: RootConfigService;
|
||||
discovery: DiscoveryService;
|
||||
skipInvalidProxies?: boolean;
|
||||
reviveConsumedRequestBodies?: boolean;
|
||||
httpRouterService: HttpRouterService;
|
||||
additionalEndpoints?: ProxyConfig;
|
||||
}
|
||||
|
||||
@@ -70,11 +67,11 @@ export interface RouterOptions {
|
||||
// given config.
|
||||
export function buildMiddleware(
|
||||
pathPrefix: string,
|
||||
logger: Logger,
|
||||
logger: LoggerService,
|
||||
route: string,
|
||||
config: string | ProxyConfig,
|
||||
httpRouterService: HttpRouterService,
|
||||
reviveConsumedRequestBodies?: boolean,
|
||||
httpRouterService?: HttpRouterService,
|
||||
): RequestHandler {
|
||||
let fullConfig: ProxyConfig;
|
||||
let credentialsPolicy: string;
|
||||
@@ -100,7 +97,7 @@ export function buildMiddleware(
|
||||
}
|
||||
|
||||
if (credentialsPolicy === 'dangerously-allow-unauthenticated') {
|
||||
httpRouterService?.addAuthPolicy({
|
||||
httpRouterService.addAuthPolicy({
|
||||
path: route,
|
||||
allow: 'unauthenticated',
|
||||
});
|
||||
@@ -154,7 +151,13 @@ export function buildMiddleware(
|
||||
}
|
||||
|
||||
// Attach the logger to the proxy config
|
||||
fullConfig.logProvider = () => logger;
|
||||
fullConfig.logProvider = () => ({
|
||||
log: logger.info.bind(logger),
|
||||
debug: logger.debug.bind(logger),
|
||||
info: logger.info.bind(logger),
|
||||
warn: logger.warn.bind(logger),
|
||||
error: logger.error.bind(logger),
|
||||
});
|
||||
// http-proxy-middleware uses this log level to check if it should log the
|
||||
// requests that it proxies. Setting this to the most verbose log level
|
||||
// ensures that it always logs these requests. Our logger ends up deciding
|
||||
@@ -229,7 +232,10 @@ export function buildMiddleware(
|
||||
return createProxyMiddleware(filter, fullConfig);
|
||||
}
|
||||
|
||||
function readProxyConfig(config: Config, logger: Logger): JsonObject {
|
||||
function readProxyConfig(
|
||||
config: RootConfigService,
|
||||
logger: LoggerService,
|
||||
): JsonObject {
|
||||
const endpoints = config
|
||||
.getOptionalConfig('proxy.endpoints')
|
||||
?.get<JsonObject>();
|
||||
@@ -256,49 +262,16 @@ function readProxyConfig(config: Config, logger: Logger): JsonObject {
|
||||
return rootEndpoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new
|
||||
* {@link https://expressjs.com/en/api.html#router | "express router"} that
|
||||
* proxies each target configured under the `proxy.endpoints` key of the config.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Example configuration:
|
||||
*
|
||||
* ```yaml
|
||||
* proxy:
|
||||
* endpoints:
|
||||
* # Option 1: Simple URL String
|
||||
* simple-example: http://simple.example.com:8080
|
||||
* # Option 2: `http-proxy-middleware` compatible object
|
||||
* '/larger-example/v1':
|
||||
* target: http://larger.example.com:8080/svc.v1
|
||||
* headers:
|
||||
* Authorization: Bearer ${EXAMPLE_AUTH_TOKEN}
|
||||
* ```
|
||||
*
|
||||
* @see https://backstage.io/docs/plugins/proxying
|
||||
* @public
|
||||
* @deprecated Please migrate to the new backend system as this will be removed in the future.
|
||||
*/
|
||||
/** @internal */
|
||||
export async function createRouter(
|
||||
options: RouterOptions,
|
||||
): Promise<express.Router> {
|
||||
return createRouterInternal(options);
|
||||
}
|
||||
|
||||
export async function createRouterInternal(
|
||||
options: RouterOptions & { httpRouterService?: HttpRouterService },
|
||||
): Promise<express.Router> {
|
||||
const router = Router();
|
||||
let currentRouter = Router();
|
||||
|
||||
const skipInvalidProxies =
|
||||
options.skipInvalidProxies ??
|
||||
options.config.getOptionalBoolean('proxy.skipInvalidProxies') ??
|
||||
false;
|
||||
options.config.getOptionalBoolean('proxy.skipInvalidProxies') ?? false;
|
||||
const reviveConsumedRequestBodies =
|
||||
options.reviveConsumedRequestBodies ??
|
||||
options.config.getOptionalBoolean('proxy.reviveConsumedRequestBodies') ??
|
||||
false;
|
||||
const proxyOptions = {
|
||||
@@ -345,7 +318,7 @@ export async function createRouterInternal(
|
||||
});
|
||||
}
|
||||
|
||||
options.httpRouterService?.use(router);
|
||||
options.httpRouterService.use(router);
|
||||
return router;
|
||||
}
|
||||
|
||||
@@ -353,12 +326,12 @@ function configureMiddlewares(
|
||||
options: {
|
||||
reviveConsumedRequestBodies: boolean;
|
||||
skipInvalidProxies: boolean;
|
||||
logger: Logger;
|
||||
logger: LoggerService;
|
||||
},
|
||||
router: express.Router,
|
||||
pathPrefix: string,
|
||||
proxyConfig: ProxyConfig,
|
||||
httpRouterService?: HttpRouterService,
|
||||
httpRouterService: HttpRouterService,
|
||||
) {
|
||||
Object.entries(proxyConfig).forEach(([route, proxyRouteConfig]) => {
|
||||
try {
|
||||
@@ -369,8 +342,8 @@ function configureMiddlewares(
|
||||
options.logger,
|
||||
route,
|
||||
proxyRouteConfig,
|
||||
options.reviveConsumedRequestBodies,
|
||||
httpRouterService,
|
||||
options.reviveConsumedRequestBodies,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
|
||||
@@ -7299,29 +7299,20 @@ __metadata:
|
||||
resolution: "@backstage/plugin-proxy-backend@workspace:plugins/proxy-backend"
|
||||
dependencies:
|
||||
"@backstage/backend-app-api": "workspace:^"
|
||||
"@backstage/backend-common": ^0.25.0
|
||||
"@backstage/backend-defaults": "workspace:^"
|
||||
"@backstage/backend-plugin-api": "workspace:^"
|
||||
"@backstage/backend-test-utils": "workspace:^"
|
||||
"@backstage/cli": "workspace:^"
|
||||
"@backstage/config": "workspace:^"
|
||||
"@backstage/config-loader": "workspace:^"
|
||||
"@backstage/errors": "workspace:^"
|
||||
"@backstage/plugin-proxy-node": "workspace:^"
|
||||
"@backstage/types": "workspace:^"
|
||||
"@types/express": ^4.17.6
|
||||
"@types/http-proxy-middleware": ^1.0.0
|
||||
"@types/yup": ^0.32.0
|
||||
express: ^4.17.1
|
||||
express-promise-router: ^4.1.0
|
||||
http-proxy-middleware: ^2.0.0
|
||||
morgan: ^1.10.0
|
||||
msw: ^2.0.0
|
||||
uuid: ^11.0.0
|
||||
winston: ^3.2.1
|
||||
yaml: ^2.0.0
|
||||
yn: ^4.0.0
|
||||
yup: ^1.0.0
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
@@ -20997,15 +20988,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/yup@npm:^0.32.0":
|
||||
version: 0.32.0
|
||||
resolution: "@types/yup@npm:0.32.0"
|
||||
dependencies:
|
||||
yup: "*"
|
||||
checksum: 5b30f1118bca288d949bcd4c7e17d1425ffa320eddd751a78c895b158fae0b6cbf242ced45ad47e9e70643c9475da09a7ad004192fb6f2111791d37e6a627e73
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/zen-observable@npm:^0.8.0":
|
||||
version: 0.8.7
|
||||
resolution: "@types/zen-observable@npm:0.8.7"
|
||||
@@ -28929,7 +28911,6 @@ __metadata:
|
||||
"@backstage/plugin-permission-backend": "workspace:^"
|
||||
"@backstage/plugin-permission-common": "workspace:^"
|
||||
"@backstage/plugin-permission-node": "workspace:^"
|
||||
"@backstage/plugin-proxy-backend": "workspace:^"
|
||||
"@backstage/plugin-scaffolder-backend": "workspace:^"
|
||||
"@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "workspace:^"
|
||||
"@backstage/plugin-scaffolder-backend-module-gitlab": "workspace:^"
|
||||
@@ -40469,13 +40450,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"property-expr@npm:^2.0.5":
|
||||
version: 2.0.6
|
||||
resolution: "property-expr@npm:2.0.6"
|
||||
checksum: 89977f4bb230736c1876f460dd7ca9328034502fd92e738deb40516d16564b850c0bbc4e052c3df88b5b8cd58e51c93b46a94bea049a3f23f4a022c038864cab
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"property-information@npm:^5.0.0":
|
||||
version: 5.6.0
|
||||
resolution: "property-information@npm:5.6.0"
|
||||
@@ -45242,13 +45216,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tiny-case@npm:^1.0.3":
|
||||
version: 1.0.3
|
||||
resolution: "tiny-case@npm:1.0.3"
|
||||
checksum: 3f7a30c39d5b0e1bc097b0b271bec14eb5b836093db034f35a0de26c14422380b50dc12bfd37498cf35b192f5df06f28a710712c87ead68872a9e37ad6f6049d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tiny-glob@npm:0.2.9":
|
||||
version: 0.2.9
|
||||
resolution: "tiny-glob@npm:0.2.9"
|
||||
@@ -45394,13 +45361,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"toposort@npm:^2.0.2":
|
||||
version: 2.0.2
|
||||
resolution: "toposort@npm:2.0.2"
|
||||
checksum: d64c74b570391c9432873f48e231b439ee56bc49f7cb9780b505cfdf5cb832f808d0bae072515d93834dd6bceca5bb34448b5b4b408335e4d4716eaf68195dcb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tosource@npm:^2.0.0-alpha.3":
|
||||
version: 2.0.0-alpha.3
|
||||
resolution: "tosource@npm:2.0.0-alpha.3"
|
||||
@@ -48089,18 +48049,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yup@npm:*, yup@npm:^1.0.0":
|
||||
version: 1.4.0
|
||||
resolution: "yup@npm:1.4.0"
|
||||
dependencies:
|
||||
property-expr: ^2.0.5
|
||||
tiny-case: ^1.0.3
|
||||
toposort: ^2.0.2
|
||||
type-fest: ^2.19.0
|
||||
checksum: 20a2ee0c1e891979ca16b34805b3a3be9ab4bea6ea3d2f9005b998b4dc992d0e4d7b53e5f4d8d9423420046630fb44fdf0ecf7e83bc34dd83392bca046c5229d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"zen-observable@npm:^0.10.0":
|
||||
version: 0.10.0
|
||||
resolution: "zen-observable@npm:0.10.0"
|
||||
|
||||
Reference in New Issue
Block a user