@backstage/core-app-api 0.4.0 → 0.5.2-next.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 +42 -0
- package/dist/index.d.ts +18 -42
- package/dist/index.esm.js +120 -196
- package/dist/index.esm.js.map +1 -1
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# @backstage/core-app-api
|
|
2
2
|
|
|
3
|
+
## 0.5.2-next.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 40775bd263: Switched out the `GithubAuth` implementation to use the common `OAuth2` implementation. This relies on the simultaneous change in `@backstage/plugin-auth-backend` that enabled access token storage in cookies rather than the current solution that's based on `LocalStorage`.
|
|
8
|
+
|
|
9
|
+
> **NOTE:** Make sure you upgrade the `auth-backend` deployment before or at the same time as you deploy this change.
|
|
10
|
+
|
|
11
|
+
## 0.5.1
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- f959c22787: Asynchronous methods on the identity API can now reliably be called at any time, including early in the bootstrap process or prior to successful sign-in.
|
|
16
|
+
|
|
17
|
+
Previously in such situations, a `Tried to access IdentityApi before app was loaded` error would be thrown. Now, those methods will wait and resolve eventually (as soon as a concrete identity API is provided).
|
|
18
|
+
|
|
19
|
+
## 0.5.0
|
|
20
|
+
|
|
21
|
+
### Minor Changes
|
|
22
|
+
|
|
23
|
+
- ceebe25391: Removed deprecated `SignInResult` type, which was replaced with the new `onSignInSuccess` callback.
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- fb565073ec: Add an `allowUrl` callback option to `FetchMiddlewares.injectIdentityAuth`
|
|
28
|
+
- 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.
|
|
29
|
+
- Updated dependencies
|
|
30
|
+
- @backstage/core-plugin-api@0.6.0
|
|
31
|
+
- @backstage/config@0.1.13
|
|
32
|
+
|
|
33
|
+
## 0.5.0-next.0
|
|
34
|
+
|
|
35
|
+
### Minor Changes
|
|
36
|
+
|
|
37
|
+
- ceebe25391: Removed deprecated `SignInResult` type, which was replaced with the new `onSignInSuccess` callback.
|
|
38
|
+
|
|
39
|
+
### Patch Changes
|
|
40
|
+
|
|
41
|
+
- Updated dependencies
|
|
42
|
+
- @backstage/core-plugin-api@0.6.0-next.0
|
|
43
|
+
- @backstage/config@0.1.13-next.0
|
|
44
|
+
|
|
3
45
|
## 0.4.0
|
|
4
46
|
|
|
5
47
|
### Minor Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ReactNode, PropsWithChildren, ComponentType } from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
|
-
import { ApiHolder, ApiRef, ApiFactory, AnyApiRef, ProfileInfo,
|
|
3
|
+
import { ApiHolder, ApiRef, ApiFactory, AnyApiRef, ProfileInfo, BackstageIdentityResponse, OAuthRequestApi, DiscoveryApi, AuthProviderInfo, githubAuthApiRef, gitlabAuthApiRef, googleAuthApiRef, OAuthApi, OpenIdConnectApi, ProfileInfoApi, BackstageIdentityApi, SessionApi, SessionState, AuthRequestOptions, oktaAuthApiRef, auth0AuthApiRef, microsoftAuthApiRef, oneloginAuthApiRef, bitbucketAuthApiRef, atlassianAuthApiRef, AlertApi, AlertMessage, AnalyticsApi, AnalyticsEvent, AppThemeApi, AppTheme, ErrorApi, ErrorApiError, ErrorApiErrorContext, FeatureFlagsApi, FeatureFlag, FeatureFlagsSaveOptions, FetchApi, IdentityApi, OAuthRequesterOptions, OAuthRequester, PendingOAuthRequest, StorageApi, StorageValueSnapshot, BackstagePlugin, IconComponent, ExternalRouteRef, AnyApiFactory, RouteRef, SubRouteRef } from '@backstage/core-plugin-api';
|
|
4
4
|
import * as _backstage_types from '@backstage/types';
|
|
5
5
|
import { Observable, JsonValue } from '@backstage/types';
|
|
6
6
|
import { Config, AppConfig } from '@backstage/config';
|
|
@@ -104,7 +104,7 @@ declare type GithubSession = {
|
|
|
104
104
|
expiresAt?: Date;
|
|
105
105
|
};
|
|
106
106
|
profile: ProfileInfo;
|
|
107
|
-
backstageIdentity:
|
|
107
|
+
backstageIdentity: BackstageIdentityResponse;
|
|
108
108
|
};
|
|
109
109
|
|
|
110
110
|
/**
|
|
@@ -130,16 +130,11 @@ declare type AuthApiCreateOptions = {
|
|
|
130
130
|
*
|
|
131
131
|
* @public
|
|
132
132
|
*/
|
|
133
|
-
declare class GithubAuth
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
signOut(): Promise<void>;
|
|
139
|
-
sessionState$(): Observable<SessionState>;
|
|
140
|
-
getAccessToken(scope?: string, options?: AuthRequestOptions): Promise<string>;
|
|
141
|
-
getBackstageIdentity(options?: AuthRequestOptions): Promise<BackstageIdentity | undefined>;
|
|
142
|
-
getProfile(options?: AuthRequestOptions): Promise<ProfileInfo | undefined>;
|
|
133
|
+
declare class GithubAuth {
|
|
134
|
+
static create(options: OAuthApiCreateOptions): typeof githubAuthApiRef.T;
|
|
135
|
+
/**
|
|
136
|
+
* @deprecated This method is deprecated and will be removed in a future release.
|
|
137
|
+
*/
|
|
143
138
|
static normalizeScope(scope?: string): Set<string>;
|
|
144
139
|
}
|
|
145
140
|
|
|
@@ -183,7 +178,7 @@ declare class OAuth2 implements OAuthApi, OpenIdConnectApi, ProfileInfoApi, Back
|
|
|
183
178
|
sessionState$(): Observable<SessionState>;
|
|
184
179
|
getAccessToken(scope?: string | string[], options?: AuthRequestOptions): Promise<string>;
|
|
185
180
|
getIdToken(options?: AuthRequestOptions): Promise<string>;
|
|
186
|
-
getBackstageIdentity(options?: AuthRequestOptions): Promise<
|
|
181
|
+
getBackstageIdentity(options?: AuthRequestOptions): Promise<BackstageIdentityResponse | undefined>;
|
|
187
182
|
getProfile(options?: AuthRequestOptions): Promise<ProfileInfo | undefined>;
|
|
188
183
|
private static normalizeScopes;
|
|
189
184
|
}
|
|
@@ -201,7 +196,7 @@ declare type OAuth2Session = {
|
|
|
201
196
|
expiresAt: Date;
|
|
202
197
|
};
|
|
203
198
|
profile: ProfileInfo;
|
|
204
|
-
backstageIdentity:
|
|
199
|
+
backstageIdentity: BackstageIdentityResponse;
|
|
205
200
|
};
|
|
206
201
|
|
|
207
202
|
/**
|
|
@@ -225,7 +220,7 @@ declare class SamlAuth implements ProfileInfoApi, BackstageIdentityApi, SessionA
|
|
|
225
220
|
private constructor();
|
|
226
221
|
signIn(): Promise<void>;
|
|
227
222
|
signOut(): Promise<void>;
|
|
228
|
-
getBackstageIdentity(options?: AuthRequestOptions): Promise<
|
|
223
|
+
getBackstageIdentity(options?: AuthRequestOptions): Promise<BackstageIdentityResponse | undefined>;
|
|
229
224
|
getProfile(options?: AuthRequestOptions): Promise<ProfileInfo | undefined>;
|
|
230
225
|
}
|
|
231
226
|
|
|
@@ -238,7 +233,7 @@ declare class SamlAuth implements ProfileInfoApi, BackstageIdentityApi, SessionA
|
|
|
238
233
|
declare type ExportedSamlSession = {
|
|
239
234
|
userId: string;
|
|
240
235
|
profile: ProfileInfo;
|
|
241
|
-
backstageIdentity:
|
|
236
|
+
backstageIdentity: BackstageIdentityResponse;
|
|
242
237
|
};
|
|
243
238
|
|
|
244
239
|
/**
|
|
@@ -307,7 +302,7 @@ declare type BitbucketSession = {
|
|
|
307
302
|
expiresAt?: Date;
|
|
308
303
|
};
|
|
309
304
|
profile: ProfileInfo;
|
|
310
|
-
backstageIdentity:
|
|
305
|
+
backstageIdentity: BackstageIdentityResponse;
|
|
311
306
|
};
|
|
312
307
|
|
|
313
308
|
/**
|
|
@@ -510,14 +505,16 @@ declare class FetchMiddlewares {
|
|
|
510
505
|
*
|
|
511
506
|
* The header injection only happens on allowlisted URLs. Per default, if the
|
|
512
507
|
* `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
|
|
508
|
+
* the `urlPrefixAllowlist` or `allowUrl` options are passed in, in which case
|
|
509
|
+
* they take precedence. If you pass in neither config nor an
|
|
510
|
+
* allowlist/callback, the middleware will have no effect since effectively no
|
|
511
|
+
* request will match the (nonexistent) rules.
|
|
516
512
|
*/
|
|
517
513
|
static injectIdentityAuth(options: {
|
|
518
514
|
identityApi: IdentityApi;
|
|
519
515
|
config?: Config;
|
|
520
516
|
urlPrefixAllowlist?: string[];
|
|
517
|
+
allowUrl?: (url: string) => boolean;
|
|
521
518
|
header?: {
|
|
522
519
|
name: string;
|
|
523
520
|
value: (backstageToken: string) => string;
|
|
@@ -578,27 +575,6 @@ declare type BootErrorPageProps = {
|
|
|
578
575
|
step: 'load-config' | 'load-chunk';
|
|
579
576
|
error: Error;
|
|
580
577
|
};
|
|
581
|
-
/**
|
|
582
|
-
* The outcome of signing in on the sign-in page.
|
|
583
|
-
*
|
|
584
|
-
* @public
|
|
585
|
-
* @deprecated replaced by passing the {@link @backstage/core-plugin-api#IdentityApi} to the {@link SignInPageProps.onSignInSuccess} instead.
|
|
586
|
-
*/
|
|
587
|
-
declare type SignInResult = {
|
|
588
|
-
/**
|
|
589
|
-
* User ID that will be returned by the IdentityApi
|
|
590
|
-
*/
|
|
591
|
-
userId: string;
|
|
592
|
-
profile: ProfileInfo;
|
|
593
|
-
/**
|
|
594
|
-
* Function used to retrieve an ID token for the signed in user.
|
|
595
|
-
*/
|
|
596
|
-
getIdToken?: () => Promise<string>;
|
|
597
|
-
/**
|
|
598
|
-
* Sign out handler that will be called if the user requests to sign out.
|
|
599
|
-
*/
|
|
600
|
-
signOut?: () => Promise<void>;
|
|
601
|
-
};
|
|
602
578
|
/**
|
|
603
579
|
* Props for the `SignInPage` component of {@link AppComponents}.
|
|
604
580
|
*
|
|
@@ -937,4 +913,4 @@ declare type FeatureFlaggedProps = {
|
|
|
937
913
|
*/
|
|
938
914
|
declare const FeatureFlagged: (props: FeatureFlaggedProps) => JSX.Element;
|
|
939
915
|
|
|
940
|
-
export { AlertApiForwarder, ApiFactoryHolder, ApiFactoryRegistry, ApiFactoryScope, ApiProvider, ApiProviderProps, ApiResolver, AppComponents, AppConfigLoader, AppContext, AppIcons, AppOptions, AppRouteBinder, AppThemeSelector, AtlassianAuth, Auth0Auth, AuthApiCreateOptions, BackstageApp, BitbucketAuth, BitbucketSession, BootErrorPageProps, ErrorAlerter, ErrorApiForwarder, ErrorBoundaryFallbackProps, FeatureFlagged, FeatureFlaggedProps, FetchMiddleware, FetchMiddlewares, FlatRoutes, FlatRoutesProps, GithubAuth, GithubSession, GitlabAuth, GoogleAuth, LocalStorageFeatureFlags, MicrosoftAuth, NoOpAnalyticsApi, OAuth2, OAuth2CreateOptions, OAuth2Session, OAuthApiCreateOptions, OAuthRequestManager, OktaAuth, OneLoginAuth, OneLoginAuthCreateOptions, SamlAuth, ExportedSamlSession as SamlSession, SignInPageProps,
|
|
916
|
+
export { AlertApiForwarder, ApiFactoryHolder, ApiFactoryRegistry, ApiFactoryScope, ApiProvider, ApiProviderProps, ApiResolver, AppComponents, AppConfigLoader, AppContext, AppIcons, AppOptions, AppRouteBinder, AppThemeSelector, AtlassianAuth, Auth0Auth, AuthApiCreateOptions, BackstageApp, BitbucketAuth, BitbucketSession, BootErrorPageProps, ErrorAlerter, ErrorApiForwarder, ErrorBoundaryFallbackProps, FeatureFlagged, FeatureFlaggedProps, FetchMiddleware, FetchMiddlewares, FlatRoutes, FlatRoutesProps, GithubAuth, GithubSession, GitlabAuth, GoogleAuth, LocalStorageFeatureFlags, MicrosoftAuth, NoOpAnalyticsApi, OAuth2, OAuth2CreateOptions, OAuth2Session, OAuthApiCreateOptions, OAuthRequestManager, OktaAuth, OneLoginAuth, OneLoginAuthCreateOptions, SamlAuth, ExportedSamlSession as SamlSession, SignInPageProps, UnhandledErrorForwarder, UrlPatternDiscovery, WebStorage, createFetchApi, createSpecializedApp, defaultConfigLoader };
|
package/dist/index.esm.js
CHANGED
|
@@ -725,167 +725,7 @@ class AuthSessionStore {
|
|
|
725
725
|
}
|
|
726
726
|
}
|
|
727
727
|
|
|
728
|
-
class OptionalRefreshSessionManagerMux {
|
|
729
|
-
constructor(options) {
|
|
730
|
-
this.stateTracker = new SessionStateTracker();
|
|
731
|
-
this.sessionCanRefresh = options.sessionCanRefresh;
|
|
732
|
-
this.staticSessionManager = options.staticSessionManager;
|
|
733
|
-
this.refreshingSessionManager = options.refreshingSessionManager;
|
|
734
|
-
}
|
|
735
|
-
async getSession(options) {
|
|
736
|
-
const staticSession = await this.staticSessionManager.getSession({
|
|
737
|
-
...options,
|
|
738
|
-
optional: true
|
|
739
|
-
});
|
|
740
|
-
if (staticSession) {
|
|
741
|
-
this.stateTracker.setIsSignedIn(true);
|
|
742
|
-
return staticSession;
|
|
743
|
-
}
|
|
744
|
-
const session = await this.refreshingSessionManager.getSession(options);
|
|
745
|
-
if (!session) {
|
|
746
|
-
this.stateTracker.setIsSignedIn(false);
|
|
747
|
-
return void 0;
|
|
748
|
-
}
|
|
749
|
-
if (this.sessionCanRefresh(session)) {
|
|
750
|
-
this.stateTracker.setIsSignedIn(true);
|
|
751
|
-
return session;
|
|
752
|
-
}
|
|
753
|
-
this.staticSessionManager.setSession(session);
|
|
754
|
-
this.stateTracker.setIsSignedIn(true);
|
|
755
|
-
return session;
|
|
756
|
-
}
|
|
757
|
-
async removeSession() {
|
|
758
|
-
await Promise.all([
|
|
759
|
-
this.refreshingSessionManager.removeSession(),
|
|
760
|
-
this.staticSessionManager.removeSession()
|
|
761
|
-
]);
|
|
762
|
-
this.stateTracker.setIsSignedIn(false);
|
|
763
|
-
}
|
|
764
|
-
sessionState$() {
|
|
765
|
-
return this.stateTracker.sessionState$();
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
const githubSessionSchema = z.object({
|
|
770
|
-
providerInfo: z.object({
|
|
771
|
-
accessToken: z.string(),
|
|
772
|
-
scopes: z.set(z.string()),
|
|
773
|
-
expiresAt: z.date().optional()
|
|
774
|
-
}),
|
|
775
|
-
profile: z.object({
|
|
776
|
-
email: z.string().optional(),
|
|
777
|
-
displayName: z.string().optional(),
|
|
778
|
-
picture: z.string().optional()
|
|
779
|
-
}),
|
|
780
|
-
backstageIdentity: z.object({
|
|
781
|
-
id: z.string(),
|
|
782
|
-
token: z.string(),
|
|
783
|
-
identity: z.object({
|
|
784
|
-
type: z.literal("user"),
|
|
785
|
-
userEntityRef: z.string(),
|
|
786
|
-
ownershipEntityRefs: z.array(z.string())
|
|
787
|
-
})
|
|
788
|
-
})
|
|
789
|
-
});
|
|
790
|
-
|
|
791
728
|
const DEFAULT_PROVIDER$a = {
|
|
792
|
-
id: "github",
|
|
793
|
-
title: "GitHub",
|
|
794
|
-
icon: () => null
|
|
795
|
-
};
|
|
796
|
-
class GithubAuth {
|
|
797
|
-
constructor(sessionManager) {
|
|
798
|
-
this.sessionManager = sessionManager;
|
|
799
|
-
}
|
|
800
|
-
static create(options) {
|
|
801
|
-
const {
|
|
802
|
-
discoveryApi,
|
|
803
|
-
environment = "development",
|
|
804
|
-
provider = DEFAULT_PROVIDER$a,
|
|
805
|
-
oauthRequestApi,
|
|
806
|
-
defaultScopes = ["read:user"]
|
|
807
|
-
} = options;
|
|
808
|
-
const connector = new DefaultAuthConnector({
|
|
809
|
-
discoveryApi,
|
|
810
|
-
environment,
|
|
811
|
-
provider,
|
|
812
|
-
oauthRequestApi,
|
|
813
|
-
sessionTransform(res) {
|
|
814
|
-
return {
|
|
815
|
-
...res,
|
|
816
|
-
providerInfo: {
|
|
817
|
-
accessToken: res.providerInfo.accessToken,
|
|
818
|
-
scopes: GithubAuth.normalizeScope(res.providerInfo.scope),
|
|
819
|
-
expiresAt: res.providerInfo.expiresInSeconds ? new Date(Date.now() + res.providerInfo.expiresInSeconds * 1e3) : void 0
|
|
820
|
-
}
|
|
821
|
-
};
|
|
822
|
-
}
|
|
823
|
-
});
|
|
824
|
-
const refreshingSessionManager = new RefreshingAuthSessionManager({
|
|
825
|
-
connector,
|
|
826
|
-
defaultScopes: new Set(defaultScopes),
|
|
827
|
-
sessionScopes: (session) => session.providerInfo.scopes,
|
|
828
|
-
sessionShouldRefresh: (session) => {
|
|
829
|
-
const { expiresAt } = session.providerInfo;
|
|
830
|
-
if (!expiresAt) {
|
|
831
|
-
return false;
|
|
832
|
-
}
|
|
833
|
-
const expiresInSec = (expiresAt.getTime() - Date.now()) / 1e3;
|
|
834
|
-
return expiresInSec < 60 * 5;
|
|
835
|
-
}
|
|
836
|
-
});
|
|
837
|
-
const staticSessionManager = new AuthSessionStore({
|
|
838
|
-
manager: new StaticAuthSessionManager({
|
|
839
|
-
connector,
|
|
840
|
-
defaultScopes: new Set(defaultScopes),
|
|
841
|
-
sessionScopes: (session) => session.providerInfo.scopes
|
|
842
|
-
}),
|
|
843
|
-
storageKey: `${provider.id}Session`,
|
|
844
|
-
schema: githubSessionSchema,
|
|
845
|
-
sessionScopes: (session) => session.providerInfo.scopes
|
|
846
|
-
});
|
|
847
|
-
const sessionManagerMux = new OptionalRefreshSessionManagerMux({
|
|
848
|
-
refreshingSessionManager,
|
|
849
|
-
staticSessionManager,
|
|
850
|
-
sessionCanRefresh: (session) => session.providerInfo.expiresAt !== void 0
|
|
851
|
-
});
|
|
852
|
-
return new GithubAuth(sessionManagerMux);
|
|
853
|
-
}
|
|
854
|
-
async signIn() {
|
|
855
|
-
await this.getAccessToken();
|
|
856
|
-
}
|
|
857
|
-
async signOut() {
|
|
858
|
-
await this.sessionManager.removeSession();
|
|
859
|
-
}
|
|
860
|
-
sessionState$() {
|
|
861
|
-
return this.sessionManager.sessionState$();
|
|
862
|
-
}
|
|
863
|
-
async getAccessToken(scope, options) {
|
|
864
|
-
var _a;
|
|
865
|
-
const session = await this.sessionManager.getSession({
|
|
866
|
-
...options,
|
|
867
|
-
scopes: GithubAuth.normalizeScope(scope)
|
|
868
|
-
});
|
|
869
|
-
return (_a = session == null ? void 0 : session.providerInfo.accessToken) != null ? _a : "";
|
|
870
|
-
}
|
|
871
|
-
async getBackstageIdentity(options = {}) {
|
|
872
|
-
const session = await this.sessionManager.getSession(options);
|
|
873
|
-
return session == null ? void 0 : session.backstageIdentity;
|
|
874
|
-
}
|
|
875
|
-
async getProfile(options = {}) {
|
|
876
|
-
const session = await this.sessionManager.getSession(options);
|
|
877
|
-
return session == null ? void 0 : session.profile;
|
|
878
|
-
}
|
|
879
|
-
static normalizeScope(scope) {
|
|
880
|
-
if (!scope) {
|
|
881
|
-
return /* @__PURE__ */ new Set();
|
|
882
|
-
}
|
|
883
|
-
const scopeList = Array.isArray(scope) ? scope : scope.split(/[\s|,]/).filter(Boolean);
|
|
884
|
-
return new Set(scopeList);
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
const DEFAULT_PROVIDER$9 = {
|
|
889
729
|
id: "oauth2",
|
|
890
730
|
title: "Your Identity Provider",
|
|
891
731
|
icon: () => null
|
|
@@ -895,7 +735,7 @@ class OAuth2 {
|
|
|
895
735
|
const {
|
|
896
736
|
discoveryApi,
|
|
897
737
|
environment = "development",
|
|
898
|
-
provider = DEFAULT_PROVIDER$
|
|
738
|
+
provider = DEFAULT_PROVIDER$a,
|
|
899
739
|
oauthRequestApi,
|
|
900
740
|
defaultScopes = [],
|
|
901
741
|
scopeTransform = (x) => x
|
|
@@ -972,6 +812,37 @@ class OAuth2 {
|
|
|
972
812
|
}
|
|
973
813
|
}
|
|
974
814
|
|
|
815
|
+
const DEFAULT_PROVIDER$9 = {
|
|
816
|
+
id: "github",
|
|
817
|
+
title: "GitHub",
|
|
818
|
+
icon: () => null
|
|
819
|
+
};
|
|
820
|
+
class GithubAuth {
|
|
821
|
+
static create(options) {
|
|
822
|
+
const {
|
|
823
|
+
discoveryApi,
|
|
824
|
+
environment = "development",
|
|
825
|
+
provider = DEFAULT_PROVIDER$9,
|
|
826
|
+
oauthRequestApi,
|
|
827
|
+
defaultScopes = ["read:user"]
|
|
828
|
+
} = options;
|
|
829
|
+
return OAuth2.create({
|
|
830
|
+
discoveryApi,
|
|
831
|
+
oauthRequestApi,
|
|
832
|
+
provider,
|
|
833
|
+
environment,
|
|
834
|
+
defaultScopes
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
static normalizeScope(scope) {
|
|
838
|
+
if (!scope) {
|
|
839
|
+
return /* @__PURE__ */ new Set();
|
|
840
|
+
}
|
|
841
|
+
const scopeList = Array.isArray(scope) ? scope : scope.split(/[\s|,]/).filter(Boolean);
|
|
842
|
+
return new Set(scopeList);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
|
|
975
846
|
const DEFAULT_PROVIDER$8 = {
|
|
976
847
|
id: "gitlab",
|
|
977
848
|
title: "GitLab",
|
|
@@ -1497,29 +1368,24 @@ function createFetchApi(options) {
|
|
|
1497
1368
|
}
|
|
1498
1369
|
|
|
1499
1370
|
class IdentityAuthInjectorFetchMiddleware {
|
|
1500
|
-
constructor(identityApi,
|
|
1371
|
+
constructor(identityApi, allowUrl, headerName, headerValue) {
|
|
1501
1372
|
this.identityApi = identityApi;
|
|
1502
|
-
this.
|
|
1373
|
+
this.allowUrl = allowUrl;
|
|
1503
1374
|
this.headerName = headerName;
|
|
1504
1375
|
this.headerValue = headerValue;
|
|
1505
1376
|
}
|
|
1506
1377
|
static create(options) {
|
|
1507
1378
|
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
|
-
}
|
|
1379
|
+
const matcher = buildMatcher(options);
|
|
1514
1380
|
const headerName = ((_a = options.header) == null ? void 0 : _a.name) || "authorization";
|
|
1515
1381
|
const headerValue = ((_b = options.header) == null ? void 0 : _b.value) || ((token) => `Bearer ${token}`);
|
|
1516
|
-
return new IdentityAuthInjectorFetchMiddleware(options.identityApi,
|
|
1382
|
+
return new IdentityAuthInjectorFetchMiddleware(options.identityApi, matcher, headerName, headerValue);
|
|
1517
1383
|
}
|
|
1518
1384
|
apply(next) {
|
|
1519
1385
|
return async (input, init) => {
|
|
1520
1386
|
const request = new Request(input, init);
|
|
1521
1387
|
const { token } = await this.identityApi.getCredentials();
|
|
1522
|
-
if (request.headers.get(this.headerName) ||
|
|
1388
|
+
if (request.headers.get(this.headerName) || typeof token !== "string" || !token || !this.allowUrl(request.url)) {
|
|
1523
1389
|
return next(input, init);
|
|
1524
1390
|
}
|
|
1525
1391
|
request.headers.set(this.headerName, this.headerValue(token));
|
|
@@ -1527,6 +1393,20 @@ class IdentityAuthInjectorFetchMiddleware {
|
|
|
1527
1393
|
};
|
|
1528
1394
|
}
|
|
1529
1395
|
}
|
|
1396
|
+
function buildMatcher(options) {
|
|
1397
|
+
if (options.allowUrl) {
|
|
1398
|
+
return options.allowUrl;
|
|
1399
|
+
} else if (options.urlPrefixAllowlist) {
|
|
1400
|
+
return buildPrefixMatcher(options.urlPrefixAllowlist);
|
|
1401
|
+
} else if (options.config) {
|
|
1402
|
+
return buildPrefixMatcher([options.config.getString("backend.baseUrl")]);
|
|
1403
|
+
}
|
|
1404
|
+
return () => false;
|
|
1405
|
+
}
|
|
1406
|
+
function buildPrefixMatcher(prefixes) {
|
|
1407
|
+
const trimmedPrefixes = prefixes.map((prefix) => prefix.replace(/\/$/, ""));
|
|
1408
|
+
return (url) => trimmedPrefixes.some((prefix) => url === prefix || url.startsWith(`${prefix}/`));
|
|
1409
|
+
}
|
|
1530
1410
|
|
|
1531
1411
|
function join(left, right) {
|
|
1532
1412
|
if (!right || right === "/") {
|
|
@@ -2138,7 +2018,7 @@ const RouteTracker = ({ tree }) => {
|
|
|
2138
2018
|
}));
|
|
2139
2019
|
};
|
|
2140
2020
|
|
|
2141
|
-
function
|
|
2021
|
+
function validateRouteParameters(routePaths, routeParents) {
|
|
2142
2022
|
const notLeafRoutes = new Set(routeParents.values());
|
|
2143
2023
|
notLeafRoutes.delete(void 0);
|
|
2144
2024
|
for (const route of routeParents.keys()) {
|
|
@@ -2167,6 +2047,21 @@ function validateRoutes(routePaths, routeParents) {
|
|
|
2167
2047
|
}
|
|
2168
2048
|
}
|
|
2169
2049
|
}
|
|
2050
|
+
function validateRouteBindings(routeBindings, plugins) {
|
|
2051
|
+
for (const plugin of plugins) {
|
|
2052
|
+
if (!plugin.externalRoutes) {
|
|
2053
|
+
continue;
|
|
2054
|
+
}
|
|
2055
|
+
for (const [name, externalRouteRef] of Object.entries(plugin.externalRoutes)) {
|
|
2056
|
+
if (externalRouteRef.optional) {
|
|
2057
|
+
continue;
|
|
2058
|
+
}
|
|
2059
|
+
if (!routeBindings.has(externalRouteRef)) {
|
|
2060
|
+
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.`);
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2170
2065
|
|
|
2171
2066
|
const AppContext = createVersionedContext("app-context");
|
|
2172
2067
|
const AppContextProvider = ({
|
|
@@ -2183,51 +2078,65 @@ const AppContextProvider = ({
|
|
|
2183
2078
|
function mkError(thing) {
|
|
2184
2079
|
return new Error(`Tried to access IdentityApi ${thing} before app was loaded`);
|
|
2185
2080
|
}
|
|
2081
|
+
function logDeprecation(thing) {
|
|
2082
|
+
console.warn(`WARNING: Call to ${thing} is deprecated and will break in the future`);
|
|
2083
|
+
}
|
|
2186
2084
|
class AppIdentityProxy {
|
|
2085
|
+
constructor() {
|
|
2086
|
+
this.resolveTarget = () => {
|
|
2087
|
+
};
|
|
2088
|
+
this.waitForTarget = new Promise((resolve) => {
|
|
2089
|
+
this.resolveTarget = resolve;
|
|
2090
|
+
});
|
|
2091
|
+
}
|
|
2187
2092
|
setTarget(identityApi) {
|
|
2188
2093
|
this.target = identityApi;
|
|
2094
|
+
this.resolveTarget(identityApi);
|
|
2189
2095
|
}
|
|
2190
2096
|
getUserId() {
|
|
2191
2097
|
if (!this.target) {
|
|
2192
2098
|
throw mkError("getUserId");
|
|
2193
2099
|
}
|
|
2100
|
+
if (!this.target.getUserId) {
|
|
2101
|
+
throw new Error("IdentityApi does not implement getUserId");
|
|
2102
|
+
}
|
|
2103
|
+
logDeprecation("getUserId");
|
|
2194
2104
|
return this.target.getUserId();
|
|
2195
2105
|
}
|
|
2196
2106
|
getProfile() {
|
|
2197
2107
|
if (!this.target) {
|
|
2198
2108
|
throw mkError("getProfile");
|
|
2199
2109
|
}
|
|
2110
|
+
if (!this.target.getProfile) {
|
|
2111
|
+
throw new Error("IdentityApi does not implement getProfile");
|
|
2112
|
+
}
|
|
2113
|
+
logDeprecation("getProfile");
|
|
2200
2114
|
return this.target.getProfile();
|
|
2201
2115
|
}
|
|
2202
2116
|
async getProfileInfo() {
|
|
2203
|
-
|
|
2204
|
-
throw mkError("getProfileInfo");
|
|
2205
|
-
}
|
|
2206
|
-
return this.target.getProfileInfo();
|
|
2117
|
+
return this.waitForTarget.then((target) => target.getProfileInfo());
|
|
2207
2118
|
}
|
|
2208
2119
|
async getBackstageIdentity() {
|
|
2209
|
-
|
|
2210
|
-
|
|
2120
|
+
const identity = await this.waitForTarget.then((target) => target.getBackstageIdentity());
|
|
2121
|
+
if (!identity.userEntityRef.match(/^.*:.*\/.*$/)) {
|
|
2122
|
+
console.warn(`WARNING: The App IdentityApi provided an invalid userEntityRef, '${identity.userEntityRef}'. It must be a full Entity Reference of the form '<kind>:<namespace>/<name>'.`);
|
|
2211
2123
|
}
|
|
2212
|
-
return
|
|
2124
|
+
return identity;
|
|
2213
2125
|
}
|
|
2214
2126
|
async getCredentials() {
|
|
2215
|
-
|
|
2216
|
-
throw mkError("getCredentials");
|
|
2217
|
-
}
|
|
2218
|
-
return this.target.getCredentials();
|
|
2127
|
+
return this.waitForTarget.then((target) => target.getCredentials());
|
|
2219
2128
|
}
|
|
2220
2129
|
async getIdToken() {
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2130
|
+
return this.waitForTarget.then((target) => {
|
|
2131
|
+
if (!target.getIdToken) {
|
|
2132
|
+
throw new Error("IdentityApi does not implement getIdToken");
|
|
2133
|
+
}
|
|
2134
|
+
logDeprecation("getIdToken");
|
|
2135
|
+
return target.getIdToken();
|
|
2136
|
+
});
|
|
2225
2137
|
}
|
|
2226
2138
|
async signOut() {
|
|
2227
|
-
|
|
2228
|
-
throw mkError("signOut");
|
|
2229
|
-
}
|
|
2230
|
-
await this.target.signOut();
|
|
2139
|
+
await this.waitForTarget.then((target) => target.signOut());
|
|
2231
2140
|
location.reload();
|
|
2232
2141
|
}
|
|
2233
2142
|
}
|
|
@@ -2342,7 +2251,7 @@ class ApiRegistry {
|
|
|
2342
2251
|
}
|
|
2343
2252
|
}
|
|
2344
2253
|
|
|
2345
|
-
function
|
|
2254
|
+
function resolveRouteBindings(bindRoutes) {
|
|
2346
2255
|
const result = /* @__PURE__ */ new Map();
|
|
2347
2256
|
if (bindRoutes) {
|
|
2348
2257
|
const bind = (externalRoutes, targetRoutes) => {
|
|
@@ -2363,6 +2272,7 @@ function generateBoundRoutes(bindRoutes) {
|
|
|
2363
2272
|
}
|
|
2364
2273
|
return result;
|
|
2365
2274
|
}
|
|
2275
|
+
|
|
2366
2276
|
function getBasePath(configApi) {
|
|
2367
2277
|
var _a;
|
|
2368
2278
|
let { pathname } = new URL((_a = configApi.getOptionalString("app.baseUrl")) != null ? _a : "/", "http://dummy.dev");
|
|
@@ -2434,9 +2344,16 @@ class AppManager {
|
|
|
2434
2344
|
}
|
|
2435
2345
|
getProvider() {
|
|
2436
2346
|
const appContext = new AppContextImpl(this);
|
|
2347
|
+
let routesHaveBeenValidated = false;
|
|
2437
2348
|
const Provider = ({ children }) => {
|
|
2438
2349
|
const appThemeApi = useMemo(() => AppThemeSelector.createWithStorage(this.themes), []);
|
|
2439
|
-
const {
|
|
2350
|
+
const {
|
|
2351
|
+
routePaths,
|
|
2352
|
+
routeParents,
|
|
2353
|
+
routeObjects,
|
|
2354
|
+
featureFlags,
|
|
2355
|
+
routeBindings
|
|
2356
|
+
} = useMemo(() => {
|
|
2440
2357
|
const result = traverseElementTree({
|
|
2441
2358
|
root: children,
|
|
2442
2359
|
discoverers: [childDiscoverer, routeElementDiscoverer],
|
|
@@ -2448,12 +2365,19 @@ class AppManager {
|
|
|
2448
2365
|
featureFlags: featureFlagCollector
|
|
2449
2366
|
}
|
|
2450
2367
|
});
|
|
2451
|
-
validateRoutes(result.routePaths, result.routeParents);
|
|
2452
2368
|
result.collectedPlugins.forEach((plugin) => this.plugins.add(plugin));
|
|
2453
2369
|
this.verifyPlugins(this.plugins);
|
|
2454
2370
|
this.getApiHolder();
|
|
2455
|
-
return
|
|
2371
|
+
return {
|
|
2372
|
+
...result,
|
|
2373
|
+
routeBindings: resolveRouteBindings(this.bindRoutes)
|
|
2374
|
+
};
|
|
2456
2375
|
}, [children]);
|
|
2376
|
+
if (!routesHaveBeenValidated) {
|
|
2377
|
+
routesHaveBeenValidated = true;
|
|
2378
|
+
validateRouteParameters(routePaths, routeParents);
|
|
2379
|
+
validateRouteBindings(routeBindings, this.plugins);
|
|
2380
|
+
}
|
|
2457
2381
|
const loadedConfig = useConfigLoader(this.configLoader, this.components, appThemeApi);
|
|
2458
2382
|
const hasConfigApi = "api" in loadedConfig;
|
|
2459
2383
|
if (hasConfigApi) {
|
|
@@ -2499,7 +2423,7 @@ class AppManager {
|
|
|
2499
2423
|
routePaths,
|
|
2500
2424
|
routeParents,
|
|
2501
2425
|
routeObjects,
|
|
2502
|
-
routeBindings
|
|
2426
|
+
routeBindings,
|
|
2503
2427
|
basePath: getBasePath(loadedConfig.api)
|
|
2504
2428
|
}, children))));
|
|
2505
2429
|
};
|