Skip to content

Commit b21ece6

Browse files
committed
feat(app): dynamic authentication provider support
This change adds support for loading authentication providers or modules from dynamic plugins. An environment variable ENABLE_AUTH_PROVIDER_MODULE_OVERRIDE controls whether or not the backend installs the default authentication provider module. When this override is enabled dynamic plugins can be used to supply custom authentication providers. This change also adds a "components" configuration for frontend dynamic plugins, which can be used to supply overrides for the AppComponents option. This is required for dynamic plugins to be able to provide a custom SignInPage component, for example: ``` frontend: my-plugin-package: components: - name: SignInPage module: PluginRoot importName: SignInPage ``` Where the named export SignInPage will be mapped to `components.SignInPage` when the frontend is initialized. Signed-off-by: Stan Lewis <[email protected]>
1 parent a37b35e commit b21ece6

File tree

4 files changed

+86
-10
lines changed

4 files changed

+86
-10
lines changed

packages/app/src/components/DynamicRoot/DynamicRoot.tsx

+39-8
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ import React, { useCallback, useEffect, useRef, useState } from 'react';
33

44
import { createApp } from '@backstage/app-defaults';
55
import { BackstageApp } from '@backstage/core-app-api';
6-
import { AnyApiFactory, BackstagePlugin } from '@backstage/core-plugin-api';
6+
import {
7+
AnyApiFactory,
8+
AppComponents,
9+
BackstagePlugin,
10+
} from '@backstage/core-plugin-api';
711

812
import { useThemes } from '@redhat-developer/red-hat-developer-hub-theme';
913
import { AppsConfig } from '@scalprum/core';
@@ -61,7 +65,9 @@ export const DynamicRoot = ({
6165
React.ComponentType | undefined
6266
>(undefined);
6367
// registry of remote components loaded at bootstrap
64-
const [components, setComponents] = useState<ComponentRegistry | undefined>();
68+
const [componentRegistry, setComponentRegistry] = useState<
69+
ComponentRegistry | undefined
70+
>();
6571
const { initialized, pluginStore, api: scalprumApi } = useScalprum();
6672

6773
const themes = useThemes();
@@ -72,6 +78,7 @@ export const DynamicRoot = ({
7278
pluginModules,
7379
apiFactories,
7480
appIcons,
81+
components,
7582
dynamicRoutes,
7683
menuItems,
7784
entityTabs,
@@ -86,6 +93,10 @@ export const DynamicRoot = ({
8693
scope,
8794
module,
8895
})),
96+
...components.map(({ scope, module }) => ({
97+
scope,
98+
module,
99+
})),
89100
...routeBindingTargets.map(({ scope, module }) => ({
90101
scope,
91102
module,
@@ -172,6 +183,23 @@ export const DynamicRoot = ({
172183
),
173184
);
174185

186+
const appComponents = components.reduce<Partial<AppComponents>>(
187+
(componentMap, { scope, module, importName, name }) => {
188+
if (typeof allPlugins[scope]?.[module]?.[importName] !== 'undefined') {
189+
componentMap[name] = allPlugins[scope]?.[module]?.[
190+
importName
191+
] as React.ComponentType<any>;
192+
} else {
193+
// eslint-disable-next-line no-console
194+
console.warn(
195+
`Plugin ${scope} is not configured properly: ${module}.${importName} not found, ignoring AppComponent: ${name}`,
196+
);
197+
}
198+
return componentMap;
199+
},
200+
{},
201+
);
202+
175203
let icons = Object.fromEntries(
176204
appIcons.reduce<[string, React.ComponentType<{}>][]>(
177205
(acc, { scope, module, importName, name }) => {
@@ -408,7 +436,10 @@ export const DynamicRoot = ({
408436
...remoteBackstagePlugins,
409437
],
410438
themes: [...filteredStaticThemes, ...dynamicThemeProviders],
411-
components: defaultAppComponents,
439+
components: {
440+
...defaultAppComponents,
441+
...appComponents,
442+
} as Partial<AppComponents>,
412443
});
413444
}
414445

@@ -424,7 +455,7 @@ export const DynamicRoot = ({
424455
scaffolderFieldExtensionComponents;
425456

426457
// make the dynamic UI configuration available to DynamicRootContext consumers
427-
setComponents({
458+
setComponentRegistry({
428459
AppProvider: app.current.getProvider(),
429460
AppRouter: app.current.getRouter(),
430461
dynamicRoutes: dynamicRoutesComponents,
@@ -449,17 +480,17 @@ export const DynamicRoot = ({
449480
]);
450481

451482
useEffect(() => {
452-
if (initialized && !components) {
483+
if (initialized && !componentRegistry) {
453484
initializeRemoteModules();
454485
}
455-
}, [initialized, components, initializeRemoteModules]);
486+
}, [initialized, componentRegistry, initializeRemoteModules]);
456487

457-
if (!initialized || !components) {
488+
if (!initialized || !componentRegistry) {
458489
return <Loader />;
459490
}
460491

461492
return (
462-
<DynamicRootContext.Provider value={components}>
493+
<DynamicRootContext.Provider value={componentRegistry}>
463494
{ChildComponent ? <ChildComponent /> : <Loader />}
464495
</DynamicRootContext.Provider>
465496
);

packages/app/src/utils/dynamicUI/extractDynamicConfig.test.ts

+16
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ describe('extractDynamicConfig', () => {
148148
const config = extractDynamicConfig(source as DynamicPluginConfig);
149149
expect(config).toEqual({
150150
pluginModules: [],
151+
components: [],
151152
routeBindings: [],
152153
dynamicRoutes: [],
153154
entityTabs: [],
@@ -162,6 +163,20 @@ describe('extractDynamicConfig', () => {
162163
});
163164

164165
it.each([
166+
[
167+
'a component',
168+
{ components: [{ name: 'foo', importName: 'blah' }] },
169+
{
170+
components: [
171+
{
172+
importName: 'blah',
173+
module: 'PluginRoot',
174+
name: 'foo',
175+
scope: 'janus-idp.plugin-foo',
176+
},
177+
],
178+
},
179+
],
165180
[
166181
'a dynamicRoute',
167182
{ dynamicRoutes: [{ path: '/foo' }] },
@@ -506,6 +521,7 @@ describe('extractDynamicConfig', () => {
506521
scope: 'janus-idp.plugin-foo',
507522
},
508523
],
524+
components: [],
509525
routeBindings: [],
510526
routeBindingTargets: [],
511527
dynamicRoutes: [],

packages/app/src/utils/dynamicUI/extractDynamicConfig.ts

+26-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Entity } from '@backstage/catalog-model';
2-
import { ApiHolder } from '@backstage/core-plugin-api';
2+
import { ApiHolder, AppComponents } from '@backstage/core-plugin-api';
33
import { isKind } from '@backstage/plugin-catalog';
44

55
import { hasAnnotation, isType } from '../../components/catalog/utils';
@@ -116,8 +116,17 @@ type ThemeEntry = {
116116
importName: string;
117117
};
118118

119+
type ComponentEntry = {
120+
scope: string;
121+
module: string;
122+
id: string;
123+
importName: string;
124+
name: keyof AppComponents;
125+
};
126+
119127
type CustomProperties = {
120128
pluginModule?: string;
129+
components: ComponentEntry[];
121130
dynamicRoutes?: (DynamicModuleEntry & {
122131
importName?: string;
123132
module?: string;
@@ -149,6 +158,7 @@ type DynamicConfig = {
149158
pluginModules: PluginModule[];
150159
apiFactories: ApiFactory[];
151160
appIcons: AppIcon[];
161+
components: ComponentEntry[];
152162
dynamicRoutes: DynamicRoute[];
153163
menuItems: MenuItem[];
154164
entityTabs: EntityTabEntry[];
@@ -171,6 +181,7 @@ function extractDynamicConfig(
171181
pluginModules: [],
172182
apiFactories: [],
173183
appIcons: [],
184+
components: [],
174185
dynamicRoutes: [],
175186
menuItems: [],
176187
entityTabs: [],
@@ -190,6 +201,20 @@ function extractDynamicConfig(
190201
},
191202
[],
192203
);
204+
config.components = Object.entries(frontend).reduce<ComponentEntry[]>(
205+
(pluginSet, [scope, customProperties]) => {
206+
pluginSet.push(
207+
...(customProperties.components ?? []).map(component => ({
208+
...component,
209+
module: component.module ?? 'PluginRoot',
210+
importName: component.importName ?? 'default',
211+
scope,
212+
})),
213+
);
214+
return pluginSet;
215+
},
216+
[],
217+
);
193218
config.dynamicRoutes = Object.entries(frontend).reduce<DynamicRoute[]>(
194219
(pluginSet, [scope, customProperties]) => {
195220
pluginSet.push(

packages/backend/src/index.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,11 @@ backend.add(rbacDynamicPluginsProvider);
9191

9292
backend.add(import('@backstage/plugin-auth-backend'));
9393
backend.add(import('@backstage/plugin-auth-backend-module-guest-provider'));
94-
backend.add(import('./modules/authProvidersModule'));
94+
if (process.env.ENABLE_AUTH_PROVIDER_MODULE_OVERRIDE !== 'true') {
95+
backend.add(import('./modules/authProvidersModule'));
96+
} else {
97+
staticLogger.info(`Default authentication provider module disabled`);
98+
}
9599

96100
backend.add(import('@internal/plugin-dynamic-plugins-info-backend'));
97101
backend.add(import('@internal/plugin-scalprum-backend'));

0 commit comments

Comments
 (0)