@backstage/core-app-api 0.5.0-next.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.esm.js +55 -16
- package/dist/index.esm.js.map +1 -1
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @backstage/core-app-api
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- ceebe25391: Removed deprecated `SignInResult` type, which was replaced with the new `onSignInSuccess` callback.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- fb565073ec: Add an `allowUrl` callback option to `FetchMiddlewares.injectIdentityAuth`
|
|
12
|
+
- f050eec2c0: Added validation during the application startup that detects if there are any plugins present that have not had their required external routes bound. Failing the validation will cause a hard crash as it is a programmer error. It lets you detect early on that there are dangling routes, rather than having them cause an error later on.
|
|
13
|
+
- Updated dependencies
|
|
14
|
+
- @backstage/core-plugin-api@0.6.0
|
|
15
|
+
- @backstage/config@0.1.13
|
|
16
|
+
|
|
3
17
|
## 0.5.0-next.0
|
|
4
18
|
|
|
5
19
|
### Minor Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -510,14 +510,16 @@ declare class FetchMiddlewares {
|
|
|
510
510
|
*
|
|
511
511
|
* The header injection only happens on allowlisted URLs. Per default, if the
|
|
512
512
|
* `config` option is passed in, the `backend.baseUrl` is allowlisted, unless
|
|
513
|
-
* the `urlPrefixAllowlist`
|
|
514
|
-
* precedence. If you pass in neither config nor an
|
|
515
|
-
* will have no effect
|
|
513
|
+
* the `urlPrefixAllowlist` or `allowUrl` options are passed in, in which case
|
|
514
|
+
* they take precedence. If you pass in neither config nor an
|
|
515
|
+
* allowlist/callback, the middleware will have no effect since effectively no
|
|
516
|
+
* request will match the (nonexistent) rules.
|
|
516
517
|
*/
|
|
517
518
|
static injectIdentityAuth(options: {
|
|
518
519
|
identityApi: IdentityApi;
|
|
519
520
|
config?: Config;
|
|
520
521
|
urlPrefixAllowlist?: string[];
|
|
522
|
+
allowUrl?: (url: string) => boolean;
|
|
521
523
|
header?: {
|
|
522
524
|
name: string;
|
|
523
525
|
value: (backstageToken: string) => string;
|
package/dist/index.esm.js
CHANGED
|
@@ -1497,29 +1497,24 @@ function createFetchApi(options) {
|
|
|
1497
1497
|
}
|
|
1498
1498
|
|
|
1499
1499
|
class IdentityAuthInjectorFetchMiddleware {
|
|
1500
|
-
constructor(identityApi,
|
|
1500
|
+
constructor(identityApi, allowUrl, headerName, headerValue) {
|
|
1501
1501
|
this.identityApi = identityApi;
|
|
1502
|
-
this.
|
|
1502
|
+
this.allowUrl = allowUrl;
|
|
1503
1503
|
this.headerName = headerName;
|
|
1504
1504
|
this.headerValue = headerValue;
|
|
1505
1505
|
}
|
|
1506
1506
|
static create(options) {
|
|
1507
1507
|
var _a, _b;
|
|
1508
|
-
const
|
|
1509
|
-
if (options.urlPrefixAllowlist) {
|
|
1510
|
-
allowlist.push(...options.urlPrefixAllowlist);
|
|
1511
|
-
} else if (options.config) {
|
|
1512
|
-
allowlist.push(options.config.getString("backend.baseUrl"));
|
|
1513
|
-
}
|
|
1508
|
+
const matcher = buildMatcher(options);
|
|
1514
1509
|
const headerName = ((_a = options.header) == null ? void 0 : _a.name) || "authorization";
|
|
1515
1510
|
const headerValue = ((_b = options.header) == null ? void 0 : _b.value) || ((token) => `Bearer ${token}`);
|
|
1516
|
-
return new IdentityAuthInjectorFetchMiddleware(options.identityApi,
|
|
1511
|
+
return new IdentityAuthInjectorFetchMiddleware(options.identityApi, matcher, headerName, headerValue);
|
|
1517
1512
|
}
|
|
1518
1513
|
apply(next) {
|
|
1519
1514
|
return async (input, init) => {
|
|
1520
1515
|
const request = new Request(input, init);
|
|
1521
1516
|
const { token } = await this.identityApi.getCredentials();
|
|
1522
|
-
if (request.headers.get(this.headerName) ||
|
|
1517
|
+
if (request.headers.get(this.headerName) || typeof token !== "string" || !token || !this.allowUrl(request.url)) {
|
|
1523
1518
|
return next(input, init);
|
|
1524
1519
|
}
|
|
1525
1520
|
request.headers.set(this.headerName, this.headerValue(token));
|
|
@@ -1527,6 +1522,20 @@ class IdentityAuthInjectorFetchMiddleware {
|
|
|
1527
1522
|
};
|
|
1528
1523
|
}
|
|
1529
1524
|
}
|
|
1525
|
+
function buildMatcher(options) {
|
|
1526
|
+
if (options.allowUrl) {
|
|
1527
|
+
return options.allowUrl;
|
|
1528
|
+
} else if (options.urlPrefixAllowlist) {
|
|
1529
|
+
return buildPrefixMatcher(options.urlPrefixAllowlist);
|
|
1530
|
+
} else if (options.config) {
|
|
1531
|
+
return buildPrefixMatcher([options.config.getString("backend.baseUrl")]);
|
|
1532
|
+
}
|
|
1533
|
+
return () => false;
|
|
1534
|
+
}
|
|
1535
|
+
function buildPrefixMatcher(prefixes) {
|
|
1536
|
+
const trimmedPrefixes = prefixes.map((prefix) => prefix.replace(/\/$/, ""));
|
|
1537
|
+
return (url) => trimmedPrefixes.some((prefix) => url === prefix || url.startsWith(`${prefix}/`));
|
|
1538
|
+
}
|
|
1530
1539
|
|
|
1531
1540
|
function join(left, right) {
|
|
1532
1541
|
if (!right || right === "/") {
|
|
@@ -2138,7 +2147,7 @@ const RouteTracker = ({ tree }) => {
|
|
|
2138
2147
|
}));
|
|
2139
2148
|
};
|
|
2140
2149
|
|
|
2141
|
-
function
|
|
2150
|
+
function validateRouteParameters(routePaths, routeParents) {
|
|
2142
2151
|
const notLeafRoutes = new Set(routeParents.values());
|
|
2143
2152
|
notLeafRoutes.delete(void 0);
|
|
2144
2153
|
for (const route of routeParents.keys()) {
|
|
@@ -2167,6 +2176,21 @@ function validateRoutes(routePaths, routeParents) {
|
|
|
2167
2176
|
}
|
|
2168
2177
|
}
|
|
2169
2178
|
}
|
|
2179
|
+
function validateRouteBindings(routeBindings, plugins) {
|
|
2180
|
+
for (const plugin of plugins) {
|
|
2181
|
+
if (!plugin.externalRoutes) {
|
|
2182
|
+
continue;
|
|
2183
|
+
}
|
|
2184
|
+
for (const [name, externalRouteRef] of Object.entries(plugin.externalRoutes)) {
|
|
2185
|
+
if (externalRouteRef.optional) {
|
|
2186
|
+
continue;
|
|
2187
|
+
}
|
|
2188
|
+
if (!routeBindings.has(externalRouteRef)) {
|
|
2189
|
+
throw new Error(`External route '${name}' of the '${plugin.getId()}' plugin must be bound to a target route. See https://backstage.io/link?bind-routes for details.`);
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2170
2194
|
|
|
2171
2195
|
const AppContext = createVersionedContext("app-context");
|
|
2172
2196
|
const AppContextProvider = ({
|
|
@@ -2361,7 +2385,7 @@ class ApiRegistry {
|
|
|
2361
2385
|
}
|
|
2362
2386
|
}
|
|
2363
2387
|
|
|
2364
|
-
function
|
|
2388
|
+
function resolveRouteBindings(bindRoutes) {
|
|
2365
2389
|
const result = /* @__PURE__ */ new Map();
|
|
2366
2390
|
if (bindRoutes) {
|
|
2367
2391
|
const bind = (externalRoutes, targetRoutes) => {
|
|
@@ -2382,6 +2406,7 @@ function generateBoundRoutes(bindRoutes) {
|
|
|
2382
2406
|
}
|
|
2383
2407
|
return result;
|
|
2384
2408
|
}
|
|
2409
|
+
|
|
2385
2410
|
function getBasePath(configApi) {
|
|
2386
2411
|
var _a;
|
|
2387
2412
|
let { pathname } = new URL((_a = configApi.getOptionalString("app.baseUrl")) != null ? _a : "/", "http://dummy.dev");
|
|
@@ -2453,9 +2478,16 @@ class AppManager {
|
|
|
2453
2478
|
}
|
|
2454
2479
|
getProvider() {
|
|
2455
2480
|
const appContext = new AppContextImpl(this);
|
|
2481
|
+
let routesHaveBeenValidated = false;
|
|
2456
2482
|
const Provider = ({ children }) => {
|
|
2457
2483
|
const appThemeApi = useMemo(() => AppThemeSelector.createWithStorage(this.themes), []);
|
|
2458
|
-
const {
|
|
2484
|
+
const {
|
|
2485
|
+
routePaths,
|
|
2486
|
+
routeParents,
|
|
2487
|
+
routeObjects,
|
|
2488
|
+
featureFlags,
|
|
2489
|
+
routeBindings
|
|
2490
|
+
} = useMemo(() => {
|
|
2459
2491
|
const result = traverseElementTree({
|
|
2460
2492
|
root: children,
|
|
2461
2493
|
discoverers: [childDiscoverer, routeElementDiscoverer],
|
|
@@ -2467,12 +2499,19 @@ class AppManager {
|
|
|
2467
2499
|
featureFlags: featureFlagCollector
|
|
2468
2500
|
}
|
|
2469
2501
|
});
|
|
2470
|
-
validateRoutes(result.routePaths, result.routeParents);
|
|
2471
2502
|
result.collectedPlugins.forEach((plugin) => this.plugins.add(plugin));
|
|
2472
2503
|
this.verifyPlugins(this.plugins);
|
|
2473
2504
|
this.getApiHolder();
|
|
2474
|
-
return
|
|
2505
|
+
return {
|
|
2506
|
+
...result,
|
|
2507
|
+
routeBindings: resolveRouteBindings(this.bindRoutes)
|
|
2508
|
+
};
|
|
2475
2509
|
}, [children]);
|
|
2510
|
+
if (!routesHaveBeenValidated) {
|
|
2511
|
+
routesHaveBeenValidated = true;
|
|
2512
|
+
validateRouteParameters(routePaths, routeParents);
|
|
2513
|
+
validateRouteBindings(routeBindings, this.plugins);
|
|
2514
|
+
}
|
|
2476
2515
|
const loadedConfig = useConfigLoader(this.configLoader, this.components, appThemeApi);
|
|
2477
2516
|
const hasConfigApi = "api" in loadedConfig;
|
|
2478
2517
|
if (hasConfigApi) {
|
|
@@ -2518,7 +2557,7 @@ class AppManager {
|
|
|
2518
2557
|
routePaths,
|
|
2519
2558
|
routeParents,
|
|
2520
2559
|
routeObjects,
|
|
2521
|
-
routeBindings
|
|
2560
|
+
routeBindings,
|
|
2522
2561
|
basePath: getBasePath(loadedConfig.api)
|
|
2523
2562
|
}, children))));
|
|
2524
2563
|
};
|