Merge pull request #8809 from backstage/rugvip/depr
removed deprecations from the 2021-12-09 release
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/core-components': patch
|
||||
---
|
||||
|
||||
Updated the `SignInPage`, `ProxiedSignInPage` and `UserIdentity` implementations to match the removals of the deprecated `IdentityApi` methods and types.
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
'@backstage/core-plugin-api': minor
|
||||
---
|
||||
|
||||
Removed deprecated `IdentityApi` methods: `getUserId`, `getIdToken`, and `getProfile`.
|
||||
|
||||
Existing usage of `getUserId` can be replaced by `getBackstageIdentity`, more precisely the equivalent of the previous `userId` can be retrieved like this:
|
||||
|
||||
```ts
|
||||
import { parseEntityRef } from '@backstage/catalog-model';
|
||||
|
||||
const identity = await identityApi.getBackstageIdentity();
|
||||
const { name: userId } = parseEntityRef(identity.userEntityRef);
|
||||
```
|
||||
|
||||
Note that it is recommended to consume the entire `userEntityRef` rather than parsing out just the name, in order to support namespaces.
|
||||
|
||||
Existing usage of `getIdToken` can be replaced by `getCredentials`, like this:
|
||||
|
||||
```ts
|
||||
const { token } = await identityApi.getCredentials();
|
||||
```
|
||||
|
||||
And existing usage of `getProfile` is replaced by `getProfileInfo`, which returns the same profile object, but is now async.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/core-app-api': minor
|
||||
'@backstage/core-plugin-api': minor
|
||||
---
|
||||
|
||||
Removed deprecated `SignInResult` type, which was replaced with the new `onSignInSuccess` callback.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-react': patch
|
||||
---
|
||||
|
||||
Deprecated `loadIdentityOwnerRefs`, since they can now be retrieved as `ownershipEntityRefs` from `identityApi.getBackstageIdentity()` instead.
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
'@backstage/plugin-azure-devops': patch
|
||||
'@backstage/plugin-badges': patch
|
||||
'@backstage/plugin-bazaar': patch
|
||||
'@backstage/plugin-catalog': patch
|
||||
'@backstage/plugin-catalog-import': patch
|
||||
'@backstage/plugin-catalog-react': patch
|
||||
'@backstage/plugin-cost-insights': patch
|
||||
'@backstage/plugin-fossa': patch
|
||||
'@backstage/plugin-ilert': patch
|
||||
'@backstage/plugin-kafka': patch
|
||||
'@backstage/plugin-kubernetes': patch
|
||||
'@backstage/plugin-pagerduty': patch
|
||||
'@backstage/plugin-permission-react': patch
|
||||
'@backstage/plugin-rollbar': patch
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
'@backstage/plugin-search': patch
|
||||
'@backstage/plugin-sentry': patch
|
||||
'@backstage/plugin-sonarqube': patch
|
||||
'@backstage/plugin-tech-insights': patch
|
||||
'@backstage/plugin-techdocs': patch
|
||||
'@backstage/plugin-todo': patch
|
||||
---
|
||||
|
||||
Migrated usage of deprecated `IdentityApi` methods.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/core-plugin-api': minor
|
||||
---
|
||||
|
||||
Removed the deprecated `id` field of `BackstageIdentityResponse`.
|
||||
|
||||
Existing usage can be replaced by parsing the `name` of the `identity.userEntityRef` with `parseEntityRef` from `@backstage/catalog-model`, although note that it is recommended to consume the entire `userEntityRef` in order to support namespaces.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/core-plugin-api': minor
|
||||
---
|
||||
|
||||
Removed deprecated `BackstageIdentity` type, which was replaced by `BackstageIdentityResponse`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/core-plugin-api': minor
|
||||
---
|
||||
|
||||
Removed deprecated `OAuthRequestApi` types: `AuthProvider`, `AuthRequesterOptions`, `AuthRequester`, and `PendingAuthRequest`.
|
||||
@@ -218,7 +218,7 @@ export class MyApi implements MyInterface {
|
||||
async getMyData() {
|
||||
const backendUrl = this.configApi.getString('backend.baseUrl');
|
||||
|
||||
+ const token = await this.identityApi.getIdToken();
|
||||
+ const { token } = await this.identityApi.getCredentials();
|
||||
const requestUrl = `${backendUrl}/api/data/`;
|
||||
- const response = await fetch(requestUrl);
|
||||
+ const response = await fetch(
|
||||
|
||||
@@ -19,8 +19,8 @@ import { atlassianAuthApiRef } from '@backstage/core-plugin-api';
|
||||
import { auth0AuthApiRef } from '@backstage/core-plugin-api';
|
||||
import { AuthProviderInfo } from '@backstage/core-plugin-api';
|
||||
import { AuthRequestOptions } from '@backstage/core-plugin-api';
|
||||
import { BackstageIdentity } from '@backstage/core-plugin-api';
|
||||
import { BackstageIdentityApi } from '@backstage/core-plugin-api';
|
||||
import { BackstageIdentityResponse } from '@backstage/core-plugin-api';
|
||||
import { BackstagePlugin } from '@backstage/core-plugin-api';
|
||||
import { bitbucketAuthApiRef } from '@backstage/core-plugin-api';
|
||||
import { ComponentType } from 'react';
|
||||
@@ -280,7 +280,7 @@ export type BitbucketSession = {
|
||||
expiresAt?: Date;
|
||||
};
|
||||
profile: ProfileInfo;
|
||||
backstageIdentity: BackstageIdentity;
|
||||
backstageIdentity: BackstageIdentityResponse;
|
||||
};
|
||||
|
||||
// @public
|
||||
@@ -386,7 +386,7 @@ export class GithubAuth implements OAuthApi, SessionApi {
|
||||
// (undocumented)
|
||||
getBackstageIdentity(
|
||||
options?: AuthRequestOptions,
|
||||
): Promise<BackstageIdentity | undefined>;
|
||||
): Promise<BackstageIdentityResponse | undefined>;
|
||||
// (undocumented)
|
||||
getProfile(options?: AuthRequestOptions): Promise<ProfileInfo | undefined>;
|
||||
// (undocumented)
|
||||
@@ -407,7 +407,7 @@ export type GithubSession = {
|
||||
expiresAt?: Date;
|
||||
};
|
||||
profile: ProfileInfo;
|
||||
backstageIdentity: BackstageIdentity;
|
||||
backstageIdentity: BackstageIdentityResponse;
|
||||
};
|
||||
|
||||
// @public
|
||||
@@ -465,7 +465,7 @@ export class OAuth2
|
||||
// (undocumented)
|
||||
getBackstageIdentity(
|
||||
options?: AuthRequestOptions,
|
||||
): Promise<BackstageIdentity | undefined>;
|
||||
): Promise<BackstageIdentityResponse | undefined>;
|
||||
// (undocumented)
|
||||
getIdToken(options?: AuthRequestOptions): Promise<string>;
|
||||
// (undocumented)
|
||||
@@ -492,7 +492,7 @@ export type OAuth2Session = {
|
||||
expiresAt: Date;
|
||||
};
|
||||
profile: ProfileInfo;
|
||||
backstageIdentity: BackstageIdentity;
|
||||
backstageIdentity: BackstageIdentityResponse;
|
||||
};
|
||||
|
||||
// @public
|
||||
@@ -540,7 +540,7 @@ export class SamlAuth
|
||||
// (undocumented)
|
||||
getBackstageIdentity(
|
||||
options?: AuthRequestOptions,
|
||||
): Promise<BackstageIdentity | undefined>;
|
||||
): Promise<BackstageIdentityResponse | undefined>;
|
||||
// (undocumented)
|
||||
getProfile(options?: AuthRequestOptions): Promise<ProfileInfo | undefined>;
|
||||
// (undocumented)
|
||||
@@ -555,7 +555,7 @@ export class SamlAuth
|
||||
export type SamlSession = {
|
||||
userId: string;
|
||||
profile: ProfileInfo;
|
||||
backstageIdentity: BackstageIdentity;
|
||||
backstageIdentity: BackstageIdentityResponse;
|
||||
};
|
||||
|
||||
// @public
|
||||
@@ -563,14 +563,6 @@ export type SignInPageProps = {
|
||||
onSignInSuccess(identityApi: IdentityApi): void;
|
||||
};
|
||||
|
||||
// @public @deprecated
|
||||
export type SignInResult = {
|
||||
userId: string;
|
||||
profile: ProfileInfo;
|
||||
getIdToken?: () => Promise<string>;
|
||||
signOut?: () => Promise<void>;
|
||||
};
|
||||
|
||||
// @public
|
||||
export class UnhandledErrorForwarder {
|
||||
static forward(errorApi: ErrorApi, errorContext: ErrorApiErrorContext): void;
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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 { withLogCollector } from '@backstage//test-utils';
|
||||
import { AppIdentityProxy } from './AppIdentityProxy';
|
||||
|
||||
describe('AppIdentityProxy', () => {
|
||||
const mockIdentityApi = {
|
||||
getBackstageIdentity: jest.fn(),
|
||||
getProfileInfo: jest.fn(),
|
||||
getCredentials: jest.fn(),
|
||||
signOut: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should forward user identities', async () => {
|
||||
const proxy = new AppIdentityProxy();
|
||||
proxy.setTarget(mockIdentityApi);
|
||||
|
||||
const logs = await withLogCollector(async () => {
|
||||
mockIdentityApi.getBackstageIdentity.mockResolvedValueOnce({
|
||||
type: 'user',
|
||||
userEntityRef: 'user:default/foo',
|
||||
ownershipEntityRefs: [],
|
||||
});
|
||||
await expect(proxy.getBackstageIdentity()).resolves.toEqual({
|
||||
type: 'user',
|
||||
userEntityRef: 'user:default/foo',
|
||||
ownershipEntityRefs: [],
|
||||
});
|
||||
});
|
||||
|
||||
expect(logs).toEqual({
|
||||
log: [],
|
||||
warn: [],
|
||||
error: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('should warn about invalid user entity refs', async () => {
|
||||
const proxy = new AppIdentityProxy();
|
||||
proxy.setTarget(mockIdentityApi);
|
||||
|
||||
const logs = await withLogCollector(async () => {
|
||||
mockIdentityApi.getBackstageIdentity.mockResolvedValueOnce({
|
||||
type: 'user',
|
||||
userEntityRef: 'bar',
|
||||
ownershipEntityRefs: [],
|
||||
});
|
||||
await expect(proxy.getBackstageIdentity()).resolves.toEqual({
|
||||
type: 'user',
|
||||
userEntityRef: 'bar',
|
||||
ownershipEntityRefs: [],
|
||||
});
|
||||
});
|
||||
|
||||
expect(logs).toEqual({
|
||||
log: [],
|
||||
warn: [
|
||||
`WARNING: The App IdentityApi provided an invalid userEntityRef, 'bar'. ` +
|
||||
`It must be a full Entity Reference of the form '<kind>:<namespace>/<name>'.`,
|
||||
],
|
||||
error: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -26,15 +26,30 @@ function mkError(thing: string) {
|
||||
);
|
||||
}
|
||||
|
||||
function logDeprecation(thing: string) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(
|
||||
`WARNING: Call to ${thing} is deprecated and will break in the future`,
|
||||
);
|
||||
}
|
||||
|
||||
// We use this for a period of backwards compatibility. It is a hidden
|
||||
// compatibility that will allow old plugins to continue working for a limited time.
|
||||
type CompatibilityIdentityApi = IdentityApi & {
|
||||
getUserId?(): string;
|
||||
getIdToken?(): Promise<string | undefined>;
|
||||
getProfile?(): ProfileInfo;
|
||||
};
|
||||
|
||||
/**
|
||||
* Implementation of the connection between the App-wide IdentityApi
|
||||
* and sign-in page.
|
||||
*/
|
||||
export class AppIdentityProxy implements IdentityApi {
|
||||
private target?: IdentityApi;
|
||||
private target?: CompatibilityIdentityApi;
|
||||
|
||||
// This is called by the app manager once the sign-in page provides us with an implementation
|
||||
setTarget(identityApi: IdentityApi) {
|
||||
setTarget(identityApi: CompatibilityIdentityApi) {
|
||||
this.target = identityApi;
|
||||
}
|
||||
|
||||
@@ -42,6 +57,10 @@ export class AppIdentityProxy implements IdentityApi {
|
||||
if (!this.target) {
|
||||
throw mkError('getUserId');
|
||||
}
|
||||
if (!this.target.getUserId) {
|
||||
throw new Error('IdentityApi does not implement getUserId');
|
||||
}
|
||||
logDeprecation('getUserId');
|
||||
return this.target.getUserId();
|
||||
}
|
||||
|
||||
@@ -49,6 +68,10 @@ export class AppIdentityProxy implements IdentityApi {
|
||||
if (!this.target) {
|
||||
throw mkError('getProfile');
|
||||
}
|
||||
if (!this.target.getProfile) {
|
||||
throw new Error('IdentityApi does not implement getProfile');
|
||||
}
|
||||
logDeprecation('getProfile');
|
||||
return this.target.getProfile();
|
||||
}
|
||||
|
||||
@@ -63,7 +86,16 @@ export class AppIdentityProxy implements IdentityApi {
|
||||
if (!this.target) {
|
||||
throw mkError('getBackstageIdentity');
|
||||
}
|
||||
return this.target.getBackstageIdentity();
|
||||
const identity = await this.target.getBackstageIdentity();
|
||||
if (!identity.userEntityRef.match(/^.*:.*\/.*$/)) {
|
||||
// eslint-disable-next-line no-console
|
||||
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>'.`,
|
||||
);
|
||||
}
|
||||
|
||||
return identity;
|
||||
}
|
||||
|
||||
async getCredentials(): Promise<{ token?: string | undefined }> {
|
||||
@@ -77,6 +109,10 @@ export class AppIdentityProxy implements IdentityApi {
|
||||
if (!this.target) {
|
||||
throw mkError('getIdToken');
|
||||
}
|
||||
if (!this.target.getIdToken) {
|
||||
throw new Error('IdentityApi does not implement getIdToken');
|
||||
}
|
||||
logDeprecation('getIdToken');
|
||||
return this.target.getIdToken();
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
BackstageIdentity,
|
||||
BackstageIdentityResponse,
|
||||
bitbucketAuthApiRef,
|
||||
ProfileInfo,
|
||||
} from '@backstage/core-plugin-api';
|
||||
@@ -30,7 +30,7 @@ export type BitbucketAuthResponse = {
|
||||
expiresInSeconds: number;
|
||||
};
|
||||
profile: ProfileInfo;
|
||||
backstageIdentity: BackstageIdentity;
|
||||
backstageIdentity: BackstageIdentityResponse;
|
||||
};
|
||||
|
||||
const DEFAULT_PROVIDER = {
|
||||
|
||||
@@ -14,7 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ProfileInfo, BackstageIdentity } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
ProfileInfo,
|
||||
BackstageIdentityResponse,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
/**
|
||||
* Session information for Bitbucket auth.
|
||||
@@ -28,5 +31,5 @@ export type BitbucketSession = {
|
||||
expiresAt?: Date;
|
||||
};
|
||||
profile: ProfileInfo;
|
||||
backstageIdentity: BackstageIdentity;
|
||||
backstageIdentity: BackstageIdentityResponse;
|
||||
};
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import {
|
||||
AuthRequestOptions,
|
||||
BackstageIdentity,
|
||||
BackstageIdentityResponse,
|
||||
OAuthApi,
|
||||
ProfileInfo,
|
||||
SessionApi,
|
||||
@@ -41,7 +41,7 @@ export type GithubAuthResponse = {
|
||||
expiresInSeconds?: number;
|
||||
};
|
||||
profile: ProfileInfo;
|
||||
backstageIdentity: BackstageIdentity;
|
||||
backstageIdentity: BackstageIdentityResponse;
|
||||
};
|
||||
|
||||
const DEFAULT_PROVIDER = {
|
||||
@@ -145,7 +145,7 @@ export default class GithubAuth implements OAuthApi, SessionApi {
|
||||
|
||||
async getBackstageIdentity(
|
||||
options: AuthRequestOptions = {},
|
||||
): Promise<BackstageIdentity | undefined> {
|
||||
): Promise<BackstageIdentityResponse | undefined> {
|
||||
const session = await this.sessionManager.getSession(options);
|
||||
return session?.backstageIdentity;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ProfileInfo, BackstageIdentity } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
ProfileInfo,
|
||||
BackstageIdentityResponse,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { z } from 'zod';
|
||||
|
||||
// TODO(Rugvip): Make GithubSession internal
|
||||
@@ -33,7 +36,7 @@ export type GithubSession = {
|
||||
};
|
||||
profile: ProfileInfo;
|
||||
// TODO(Rugvip): This should be made optional once the type is no longer public
|
||||
backstageIdentity: BackstageIdentity;
|
||||
backstageIdentity: BackstageIdentityResponse;
|
||||
};
|
||||
|
||||
export const githubSessionSchema: z.ZodSchema<GithubSession> = z.object({
|
||||
|
||||
@@ -19,7 +19,7 @@ import { RefreshingAuthSessionManager } from '../../../../lib/AuthSessionManager
|
||||
import { SessionManager } from '../../../../lib/AuthSessionManager/types';
|
||||
import {
|
||||
AuthRequestOptions,
|
||||
BackstageIdentity,
|
||||
BackstageIdentityResponse,
|
||||
OAuthApi,
|
||||
OpenIdConnectApi,
|
||||
ProfileInfo,
|
||||
@@ -48,7 +48,7 @@ export type OAuth2Response = {
|
||||
expiresInSeconds: number;
|
||||
};
|
||||
profile: ProfileInfo;
|
||||
backstageIdentity: BackstageIdentity;
|
||||
backstageIdentity: BackstageIdentityResponse;
|
||||
};
|
||||
|
||||
const DEFAULT_PROVIDER = {
|
||||
@@ -159,7 +159,7 @@ export default class OAuth2
|
||||
|
||||
async getBackstageIdentity(
|
||||
options: AuthRequestOptions = {},
|
||||
): Promise<BackstageIdentity | undefined> {
|
||||
): Promise<BackstageIdentityResponse | undefined> {
|
||||
const session = await this.sessionManager.getSession(options);
|
||||
return session?.backstageIdentity;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ProfileInfo, BackstageIdentity } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
ProfileInfo,
|
||||
BackstageIdentityResponse,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
export type { OAuth2CreateOptions } from './OAuth2';
|
||||
/**
|
||||
@@ -30,5 +33,5 @@ export type OAuth2Session = {
|
||||
expiresAt: Date;
|
||||
};
|
||||
profile: ProfileInfo;
|
||||
backstageIdentity: BackstageIdentity;
|
||||
backstageIdentity: BackstageIdentityResponse;
|
||||
};
|
||||
|
||||
@@ -16,12 +16,12 @@
|
||||
|
||||
import {
|
||||
AuthRequestOptions,
|
||||
BackstageIdentity,
|
||||
BackstageIdentityApi,
|
||||
ProfileInfo,
|
||||
ProfileInfoApi,
|
||||
SessionApi,
|
||||
SessionState,
|
||||
BackstageIdentityResponse,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { Observable } from '@backstage/types';
|
||||
import { DirectAuthConnector } from '../../../../lib/AuthConnector';
|
||||
@@ -35,7 +35,7 @@ import { SamlSession, samlSessionSchema } from './types';
|
||||
|
||||
export type SamlAuthResponse = {
|
||||
profile: ProfileInfo;
|
||||
backstageIdentity: BackstageIdentity;
|
||||
backstageIdentity: BackstageIdentityResponse;
|
||||
};
|
||||
|
||||
const DEFAULT_PROVIDER = {
|
||||
@@ -95,7 +95,7 @@ export default class SamlAuth
|
||||
|
||||
async getBackstageIdentity(
|
||||
options: AuthRequestOptions = {},
|
||||
): Promise<BackstageIdentity | undefined> {
|
||||
): Promise<BackstageIdentityResponse | undefined> {
|
||||
const session = await this.sessionManager.getSession(options);
|
||||
return session?.backstageIdentity;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { BackstageIdentity, ProfileInfo } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
BackstageIdentityResponse,
|
||||
ProfileInfo,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
@@ -26,13 +29,13 @@ import { z } from 'zod';
|
||||
export type ExportedSamlSession = {
|
||||
userId: string;
|
||||
profile: ProfileInfo;
|
||||
backstageIdentity: BackstageIdentity;
|
||||
backstageIdentity: BackstageIdentityResponse;
|
||||
};
|
||||
|
||||
/** @internal */
|
||||
export type SamlSession = {
|
||||
profile: ProfileInfo;
|
||||
backstageIdentity: BackstageIdentity;
|
||||
backstageIdentity: BackstageIdentityResponse;
|
||||
};
|
||||
|
||||
/** @internal */
|
||||
|
||||
@@ -18,7 +18,6 @@ import { ComponentType } from 'react';
|
||||
import {
|
||||
AnyApiFactory,
|
||||
AppTheme,
|
||||
ProfileInfo,
|
||||
IconComponent,
|
||||
BackstagePlugin,
|
||||
RouteRef,
|
||||
@@ -38,31 +37,6 @@ export type BootErrorPageProps = {
|
||||
error: Error;
|
||||
};
|
||||
|
||||
/**
|
||||
* The outcome of signing in on the sign-in page.
|
||||
*
|
||||
* @public
|
||||
* @deprecated replaced by passing the {@link @backstage/core-plugin-api#IdentityApi} to the {@link SignInPageProps.onSignInSuccess} instead.
|
||||
*/
|
||||
export type SignInResult = {
|
||||
/**
|
||||
* User ID that will be returned by the IdentityApi
|
||||
*/
|
||||
userId: string;
|
||||
|
||||
profile: ProfileInfo;
|
||||
|
||||
/**
|
||||
* Function used to retrieve an ID token for the signed in user.
|
||||
*/
|
||||
getIdToken?: () => Promise<string>;
|
||||
|
||||
/**
|
||||
* Sign out handler that will be called if the user requests to sign out.
|
||||
*/
|
||||
signOut?: () => Promise<void>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Props for the `SignInPage` component of {@link AppComponents}.
|
||||
*
|
||||
|
||||
@@ -39,7 +39,6 @@ import { ReactElement } from 'react';
|
||||
import { ReactNode } from 'react';
|
||||
import { SessionApi } from '@backstage/core-plugin-api';
|
||||
import { SignInPageProps } from '@backstage/core-plugin-api';
|
||||
import { SignInResult } from '@backstage/core-plugin-api';
|
||||
import { SparklinesLineProps } from 'react-sparklines';
|
||||
import { SparklinesProps } from 'react-sparklines';
|
||||
import { StyledComponentProps } from '@material-ui/core/styles';
|
||||
@@ -2424,7 +2423,12 @@ export class UserIdentity implements IdentityApi {
|
||||
profile?: ProfileInfo;
|
||||
}): IdentityApi;
|
||||
static createGuest(): IdentityApi;
|
||||
static fromLegacy(result: SignInResult): IdentityApi;
|
||||
static fromLegacy(result: {
|
||||
userId: string;
|
||||
profile: ProfileInfo;
|
||||
getIdToken?: () => Promise<string>;
|
||||
signOut?: () => Promise<void>;
|
||||
}): IdentityApi;
|
||||
// (undocumented)
|
||||
getBackstageIdentity(): Promise<BackstageUserIdentity>;
|
||||
// (undocumented)
|
||||
|
||||
@@ -20,30 +20,46 @@ import {
|
||||
ProfileInfo,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
// Similar to the AppIdentityApi we provide backwards compatibility for a limited time
|
||||
type CompatibilityIdentityApi = IdentityApi & {
|
||||
getUserId?(): string;
|
||||
getIdToken?(): Promise<string | undefined>;
|
||||
getProfile?(): ProfileInfo;
|
||||
};
|
||||
|
||||
export class IdentityApiSignOutProxy implements IdentityApi {
|
||||
private constructor(
|
||||
private readonly config: {
|
||||
identityApi: IdentityApi;
|
||||
identityApi: CompatibilityIdentityApi;
|
||||
signOut: IdentityApi['signOut'];
|
||||
},
|
||||
) {}
|
||||
|
||||
static from(config: {
|
||||
identityApi: IdentityApi;
|
||||
identityApi: CompatibilityIdentityApi;
|
||||
signOut: IdentityApi['signOut'];
|
||||
}): IdentityApi {
|
||||
return new IdentityApiSignOutProxy(config);
|
||||
}
|
||||
|
||||
getUserId(): string {
|
||||
if (!this.config.identityApi.getUserId) {
|
||||
throw new Error(`SignOutProxy IdentityApi.getUserId is not implemented`);
|
||||
}
|
||||
return this.config.identityApi.getUserId();
|
||||
}
|
||||
|
||||
getIdToken(): Promise<string | undefined> {
|
||||
if (!this.config.identityApi.getIdToken) {
|
||||
throw new Error(`SignOutProxy IdentityApi.getIdToken is not implemented`);
|
||||
}
|
||||
return this.config.identityApi.getIdToken();
|
||||
}
|
||||
|
||||
getProfile(): ProfileInfo {
|
||||
if (!this.config.identityApi.getProfile) {
|
||||
throw new Error(`SignOutProxy IdentityApi.getProfile is not implemented`);
|
||||
}
|
||||
return this.config.identityApi.getProfile();
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
IdentityApi,
|
||||
ProfileInfo,
|
||||
BackstageUserIdentity,
|
||||
SignInResult,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
function parseJwtPayload(token: string) {
|
||||
@@ -26,14 +25,22 @@ function parseJwtPayload(token: string) {
|
||||
return JSON.parse(atob(payload));
|
||||
}
|
||||
|
||||
type LegacySignInResult = {
|
||||
userId: string;
|
||||
profile: ProfileInfo;
|
||||
getIdToken?: () => Promise<string>;
|
||||
signOut?: () => Promise<void>;
|
||||
};
|
||||
|
||||
/** @internal */
|
||||
export class LegacyUserIdentity implements IdentityApi {
|
||||
private constructor(private readonly result: SignInResult) {}
|
||||
private constructor(private readonly result: LegacySignInResult) {}
|
||||
|
||||
getUserId(): string {
|
||||
return this.result.userId;
|
||||
}
|
||||
|
||||
static fromResult(result: SignInResult): LegacyUserIdentity {
|
||||
static fromResult(result: LegacySignInResult): LegacyUserIdentity {
|
||||
return new LegacyUserIdentity(result);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,12 +21,17 @@ import {
|
||||
BackstageUserIdentity,
|
||||
BackstageIdentityApi,
|
||||
SessionApi,
|
||||
SignInResult,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
import { GuestUserIdentity } from './GuestUserIdentity';
|
||||
import { LegacyUserIdentity } from './LegacyUserIdentity';
|
||||
|
||||
// TODO(Rugvip): This and the other IdentityApi implementations still implement
|
||||
// the old removed methods. This is to allow for backwards compatibility
|
||||
// with old plugins that still consume this API. We will leave these in
|
||||
// place as a hidden compatibility for a couple of months.
|
||||
// The AppIdentityProxy warns in case any of these methods are called.
|
||||
|
||||
/**
|
||||
* An implementation of the IdentityApi that is constructed using
|
||||
* various backstage user identity representations.
|
||||
@@ -49,7 +54,24 @@ export class UserIdentity implements IdentityApi {
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
static fromLegacy(result: SignInResult): IdentityApi {
|
||||
static fromLegacy(result: {
|
||||
/**
|
||||
* User ID that will be returned by the IdentityApi
|
||||
*/
|
||||
userId: string;
|
||||
|
||||
profile: ProfileInfo;
|
||||
|
||||
/**
|
||||
* Function used to retrieve an ID token for the signed in user.
|
||||
*/
|
||||
getIdToken?: () => Promise<string>;
|
||||
|
||||
/**
|
||||
* Sign out handler that will be called if the user requests to sign out.
|
||||
*/
|
||||
signOut?: () => Promise<void>;
|
||||
}): IdentityApi {
|
||||
return LegacyUserIdentity.fromResult(result);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import { IconComponent as IconComponent_2 } from '@backstage/core-plugin-api';
|
||||
import { IdentityApi as IdentityApi_2 } from '@backstage/core-plugin-api';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { Observable } from '@backstage/types';
|
||||
import { ProfileInfo as ProfileInfo_2 } from '@backstage/core-plugin-api';
|
||||
import { default as React_2 } from 'react';
|
||||
import { ReactElement } from 'react';
|
||||
import { ReactNode } from 'react';
|
||||
@@ -195,9 +194,6 @@ export const auth0AuthApiRef: ApiRef<
|
||||
OpenIdConnectApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
|
||||
>;
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export type AuthProvider = Omit<AuthProviderInfo, 'id'>;
|
||||
|
||||
// @public
|
||||
export type AuthProviderInfo = {
|
||||
id: string;
|
||||
@@ -205,21 +201,12 @@ export type AuthProviderInfo = {
|
||||
icon: IconComponent;
|
||||
};
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export type AuthRequester<T> = OAuthRequester<T>;
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export type AuthRequesterOptions<T> = OAuthRequesterOptions<T>;
|
||||
|
||||
// @public
|
||||
export type AuthRequestOptions = {
|
||||
optional?: boolean;
|
||||
instantPopup?: boolean;
|
||||
};
|
||||
|
||||
// @public @deprecated
|
||||
export type BackstageIdentity = BackstageIdentityResponse;
|
||||
|
||||
// @public
|
||||
export type BackstageIdentityApi = {
|
||||
getBackstageIdentity(
|
||||
@@ -229,7 +216,6 @@ export type BackstageIdentityApi = {
|
||||
|
||||
// @public
|
||||
export type BackstageIdentityResponse = {
|
||||
id: string;
|
||||
token: string;
|
||||
identity: BackstageUserIdentity;
|
||||
};
|
||||
@@ -512,9 +498,6 @@ export type IconComponent = ComponentType<{
|
||||
|
||||
// @public
|
||||
export type IdentityApi = {
|
||||
getUserId(): string;
|
||||
getIdToken(): Promise<string | undefined>;
|
||||
getProfile(): ProfileInfo;
|
||||
getProfileInfo(): Promise<ProfileInfo>;
|
||||
getBackstageIdentity(): Promise<BackstageUserIdentity>;
|
||||
getCredentials(): Promise<{
|
||||
@@ -657,9 +640,6 @@ export type PathParams<S extends string> = {
|
||||
[name in ParamNames<S>]: string;
|
||||
};
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export type PendingAuthRequest = PendingOAuthRequest;
|
||||
|
||||
// @public
|
||||
export type PendingOAuthRequest = {
|
||||
provider: Omit<AuthProviderInfo, 'id'> & {
|
||||
@@ -732,14 +712,6 @@ export type SignInPageProps = {
|
||||
onSignInSuccess(identityApi: IdentityApi_2): void;
|
||||
};
|
||||
|
||||
// @public @deprecated
|
||||
export type SignInResult = {
|
||||
userId: string;
|
||||
profile: ProfileInfo_2;
|
||||
getIdToken?: () => Promise<string>;
|
||||
signOut?: () => Promise<void>;
|
||||
};
|
||||
|
||||
// @public
|
||||
export interface StorageApi {
|
||||
forBucket(name: string): StorageApi;
|
||||
|
||||
@@ -22,31 +22,6 @@ import { BackstageUserIdentity, ProfileInfo } from './auth';
|
||||
* @public
|
||||
*/
|
||||
export type IdentityApi = {
|
||||
/**
|
||||
* The ID of the signed in user. This ID is not meant to be presented to the user, but used
|
||||
* as an opaque string to pass on to backends or use in frontend logic.
|
||||
*
|
||||
* @deprecated use {@link IdentityApi.getBackstageIdentity} instead.
|
||||
*/
|
||||
getUserId(): string;
|
||||
|
||||
/**
|
||||
* An OpenID Connect ID Token which proves the identity of the signed in user.
|
||||
*
|
||||
* The ID token will be undefined if the signed in user does not have a verified
|
||||
* identity, such as a demo user or mocked user for e2e tests.
|
||||
*
|
||||
* @deprecated use {@link IdentityApi.getCredentials} instead.
|
||||
*/
|
||||
getIdToken(): Promise<string | undefined>;
|
||||
|
||||
/**
|
||||
* The profile of the signed in user.
|
||||
*
|
||||
* @deprecated use {@link IdentityApi.getProfileInfo} instead.
|
||||
*/
|
||||
getProfile(): ProfileInfo;
|
||||
|
||||
/**
|
||||
* The profile of the signed in user.
|
||||
*/
|
||||
|
||||
@@ -18,12 +18,6 @@ import { Observable } from '@backstage/types';
|
||||
import { ApiRef, createApiRef } from '../system';
|
||||
import { AuthProviderInfo } from './auth';
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @deprecated Use AuthProviderInfo instead
|
||||
*/
|
||||
export type AuthProvider = Omit<AuthProviderInfo, 'id'>;
|
||||
|
||||
/**
|
||||
* Describes how to handle auth requests. Both how to show them to the user, and what to do when
|
||||
* the user accesses the auth request.
|
||||
@@ -45,12 +39,6 @@ export type OAuthRequesterOptions<TOAuthResponse> = {
|
||||
onAuthRequest(scopes: Set<string>): Promise<TOAuthResponse>;
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @deprecated Use OAuthRequesterOptions instead
|
||||
*/
|
||||
export type AuthRequesterOptions<T> = OAuthRequesterOptions<T>;
|
||||
|
||||
/**
|
||||
* Function used to trigger new auth requests for a set of scopes.
|
||||
*
|
||||
@@ -69,12 +57,6 @@ export type OAuthRequester<TAuthResponse> = (
|
||||
scopes: Set<string>,
|
||||
) => Promise<TAuthResponse>;
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @deprecated Use OAuthRequester instead
|
||||
*/
|
||||
export type AuthRequester<T> = OAuthRequester<T>;
|
||||
|
||||
/**
|
||||
* An pending auth request for a single auth provider. The request will remain in this pending
|
||||
* state until either reject() or trigger() is called.
|
||||
@@ -107,12 +89,6 @@ export type PendingOAuthRequest = {
|
||||
trigger(): Promise<void>;
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @deprecated Use PendingOAuthRequest instead
|
||||
*/
|
||||
export type PendingAuthRequest = PendingOAuthRequest;
|
||||
|
||||
/**
|
||||
* Provides helpers for implemented OAuth login flows within Backstage.
|
||||
*
|
||||
|
||||
@@ -221,13 +221,6 @@ export type BackstageUserIdentity = {
|
||||
* @public
|
||||
*/
|
||||
export type BackstageIdentityResponse = {
|
||||
/**
|
||||
* The backstage user ID.
|
||||
*
|
||||
* @deprecated The identity is now provided via the `identity` field instead.
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* The token used to authenticate the user within Backstage.
|
||||
*/
|
||||
@@ -239,14 +232,6 @@ export type BackstageIdentityResponse = {
|
||||
identity: BackstageUserIdentity;
|
||||
};
|
||||
|
||||
/**
|
||||
* The old exported symbol for {@link BackstageIdentityResponse}.
|
||||
*
|
||||
* @public
|
||||
* @deprecated use {@link BackstageIdentityResponse} instead.
|
||||
*/
|
||||
export type BackstageIdentity = BackstageIdentityResponse;
|
||||
|
||||
/**
|
||||
* Profile information of the user.
|
||||
*
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
export type {
|
||||
BootErrorPageProps,
|
||||
SignInResult,
|
||||
SignInPageProps,
|
||||
ErrorBoundaryFallbackProps,
|
||||
AppComponents,
|
||||
|
||||
@@ -96,7 +96,7 @@ export class AzureDevOpsClient implements AzureDevOpsApi {
|
||||
const baseUrl = `${await this.discoveryApi.getBaseUrl('azure-devops')}/`;
|
||||
const url = new URL(path, baseUrl);
|
||||
|
||||
const idToken = await this.identityApi.getIdToken();
|
||||
const { token: idToken } = await this.identityApi.getCredentials();
|
||||
const response = await fetch(url.toString(), {
|
||||
headers: idToken ? { Authorization: `Bearer ${idToken}` } : {},
|
||||
});
|
||||
|
||||
@@ -15,8 +15,10 @@
|
||||
*/
|
||||
|
||||
import { identityApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
|
||||
export function useUserEmail(): string | undefined {
|
||||
const identityApi = useApi(identityApiRef);
|
||||
return identityApi.getProfile().email;
|
||||
const state = useAsync(() => identityApi.getProfileInfo(), [identityApi]);
|
||||
return state.value?.email;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ export class BadgesClient implements BadgesApi {
|
||||
|
||||
public async getEntityBadgeSpecs(entity: Entity): Promise<BadgeSpec[]> {
|
||||
const entityBadgeSpecsUrl = await this.getEntityBadgeSpecsUrl(entity);
|
||||
const token = await this.identityApi.getIdToken();
|
||||
const { token } = await this.identityApi.getCredentials();
|
||||
const response = await fetch(entityBadgeSpecsUrl, {
|
||||
headers: token
|
||||
? {
|
||||
|
||||
@@ -121,6 +121,7 @@ export class BazaarClient implements BazaarApi {
|
||||
|
||||
async addMember(id: number, userId: string): Promise<void> {
|
||||
const baseUrl = await this.discoveryApi.getBaseUrl('bazaar');
|
||||
const { picture } = await this.identityApi.getProfileInfo();
|
||||
|
||||
await fetch(
|
||||
`${baseUrl}/projects/${encodeURIComponent(
|
||||
@@ -132,9 +133,7 @@ export class BazaarClient implements BazaarApi {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
picture: (await this.identityApi.getProfileInfo()).picture,
|
||||
}),
|
||||
body: JSON.stringify({ picture }),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -67,21 +67,12 @@ describe('CatalogImportClient', () => {
|
||||
getCredentials: jest.fn().mockResolvedValue({ token: 'token' }),
|
||||
};
|
||||
const identityApi = {
|
||||
getUserId: () => {
|
||||
return 'user';
|
||||
},
|
||||
getProfile: () => {
|
||||
return {};
|
||||
},
|
||||
getIdToken: () => {
|
||||
return Promise.resolve('token');
|
||||
},
|
||||
signOut: () => {
|
||||
return Promise.resolve();
|
||||
},
|
||||
getProfileInfo: jest.fn(),
|
||||
getBackstageIdentity: jest.fn(),
|
||||
getCredentials: jest.fn(),
|
||||
getCredentials: jest.fn().mockResolvedValue({ token: 'token' }),
|
||||
};
|
||||
|
||||
const scmIntegrationsApi = ScmIntegrations.fromConfig(
|
||||
|
||||
@@ -173,13 +173,13 @@ the component will become available.\n\nFor more information, read an \
|
||||
}: {
|
||||
repo: string;
|
||||
}): Promise<PartialEntity[]> {
|
||||
const idToken = await this.identityApi.getIdToken();
|
||||
const { token } = await this.identityApi.getCredentials();
|
||||
const response = await fetch(
|
||||
`${await this.discoveryApi.getBaseUrl('catalog')}/analyze-location`,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(idToken && { Authorization: `Bearer ${idToken}` }),
|
||||
...(token && { Authorization: `Bearer ${token}` }),
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
|
||||
@@ -781,7 +781,7 @@ export function loadCatalogOwnerRefs(
|
||||
identityOwnerRefs: string[],
|
||||
): Promise<string[]>;
|
||||
|
||||
// @public
|
||||
// @public @deprecated
|
||||
export function loadIdentityOwnerRefs(
|
||||
identityApi: IdentityApi,
|
||||
): Promise<string[]>;
|
||||
|
||||
@@ -68,8 +68,12 @@ const mockConfigApi = {
|
||||
getOptionalString: () => '',
|
||||
} as Partial<ConfigApi>;
|
||||
const mockIdentityApi: Partial<IdentityApi> = {
|
||||
getUserId: () => 'guest',
|
||||
getIdToken: async () => undefined,
|
||||
getBackstageIdentity: async () => ({
|
||||
type: 'user',
|
||||
userEntityRef: 'user:default/guest',
|
||||
ownershipEntityRefs: [],
|
||||
}),
|
||||
getCredentials: async () => ({ token: undefined }),
|
||||
};
|
||||
const mockCatalogApi: Partial<CatalogApi> = {
|
||||
getEntities: jest.fn().mockImplementation(async () => ({ items: entities })),
|
||||
|
||||
@@ -33,14 +33,11 @@ import {
|
||||
} from './useEntityOwnership';
|
||||
|
||||
describe('useEntityOwnership', () => {
|
||||
type MockIdentityApi = jest.Mocked<
|
||||
Pick<IdentityApi, 'getUserId' | 'getIdToken'>
|
||||
>;
|
||||
type MockIdentityApi = jest.Mocked<Pick<IdentityApi, 'getBackstageIdentity'>>;
|
||||
type MockCatalogApi = jest.Mocked<Pick<CatalogApi, 'getEntityByName'>>;
|
||||
|
||||
const mockIdentityApi: MockIdentityApi = {
|
||||
getUserId: jest.fn(),
|
||||
getIdToken: jest.fn(),
|
||||
getBackstageIdentity: jest.fn(),
|
||||
};
|
||||
const mockCatalogApi: MockCatalogApi = {
|
||||
getEntityByName: jest.fn(),
|
||||
@@ -100,80 +97,19 @@ describe('useEntityOwnership', () => {
|
||||
],
|
||||
};
|
||||
|
||||
// these were generated on https://jwt.io, based off of its default example token
|
||||
// no ent at all
|
||||
const tokenNoEnt =
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
|
||||
// "ent": []
|
||||
const tokenEmptyEnt =
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJlbnQiOltdfQ.Khyza2whczkoC4wSCLBhBaBB9-ktIkk7gpXEgQPHhtY';
|
||||
// "ent": ["user:default/user1"]
|
||||
const tokenUserEnt =
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJlbnQiOlsidXNlcjpkZWZhdWx0L3VzZXIxIl19.CMCxjwI4rj_TD3uUoBNgFjkZI23LwRTbQnSPBxzncoY';
|
||||
// "ent": ["user:default/user1", "group:default/group1"]
|
||||
const tokenUserAndGroupEnt =
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJlbnQiOlsidXNlcjpkZWZhdWx0L3VzZXIxIiwiZ3JvdXA6ZGVmYXVsdC9ncm91cDEiXX0.ZZmZrogbQKx0hnForw63ETkyAhUyeoBE8Hgloi45rdg';
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('loadIdentityOwnerRefs', () => {
|
||||
it('returns the user id when there is no relevant token info', async () => {
|
||||
mockIdentityApi.getUserId.mockReturnValueOnce('foo');
|
||||
mockIdentityApi.getIdToken.mockResolvedValueOnce(undefined);
|
||||
await expect(loadIdentityOwnerRefs(identityApi)).resolves.toEqual([
|
||||
'user:default/foo',
|
||||
]);
|
||||
|
||||
mockIdentityApi.getUserId.mockReturnValueOnce('ns/foo');
|
||||
mockIdentityApi.getIdToken.mockResolvedValueOnce(undefined);
|
||||
await expect(loadIdentityOwnerRefs(identityApi)).resolves.toEqual([
|
||||
'user:ns/foo',
|
||||
]);
|
||||
|
||||
mockIdentityApi.getUserId.mockReturnValueOnce('user:ns/foo');
|
||||
mockIdentityApi.getIdToken.mockResolvedValueOnce(undefined);
|
||||
await expect(loadIdentityOwnerRefs(identityApi)).resolves.toEqual([
|
||||
'user:ns/foo',
|
||||
]);
|
||||
|
||||
mockIdentityApi.getUserId.mockReturnValueOnce('foo');
|
||||
mockIdentityApi.getIdToken.mockResolvedValueOnce(tokenNoEnt);
|
||||
await expect(loadIdentityOwnerRefs(identityApi)).resolves.toEqual([
|
||||
'user:default/foo',
|
||||
]);
|
||||
|
||||
mockIdentityApi.getUserId.mockReturnValueOnce('foo');
|
||||
mockIdentityApi.getIdToken.mockResolvedValueOnce(tokenEmptyEnt);
|
||||
await expect(loadIdentityOwnerRefs(identityApi)).resolves.toEqual([
|
||||
'user:default/foo',
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns both the user id and the token parts', async () => {
|
||||
mockIdentityApi.getUserId.mockReturnValueOnce('foo');
|
||||
mockIdentityApi.getIdToken.mockResolvedValueOnce(tokenUserEnt);
|
||||
await expect(loadIdentityOwnerRefs(identityApi)).resolves.toEqual([
|
||||
'user:default/foo',
|
||||
'user:default/user1',
|
||||
]);
|
||||
|
||||
mockIdentityApi.getUserId.mockReturnValueOnce('foo');
|
||||
mockIdentityApi.getIdToken.mockResolvedValueOnce(tokenUserAndGroupEnt);
|
||||
await expect(loadIdentityOwnerRefs(identityApi)).resolves.toEqual([
|
||||
'user:default/foo',
|
||||
'user:default/user1',
|
||||
'group:default/group1',
|
||||
]);
|
||||
});
|
||||
|
||||
it('gracefully ignores broken token', async () => {
|
||||
mockIdentityApi.getUserId.mockReturnValueOnce('foo');
|
||||
mockIdentityApi.getIdToken.mockResolvedValueOnce('not a jwt');
|
||||
await expect(loadIdentityOwnerRefs(identityApi)).resolves.toEqual([
|
||||
'user:default/foo',
|
||||
]);
|
||||
it('passes through the ownershipEntityRefs', async () => {
|
||||
const refs = new Array<string>();
|
||||
mockIdentityApi.getBackstageIdentity.mockResolvedValueOnce({
|
||||
type: 'user',
|
||||
userEntityRef: 'user:default/guest',
|
||||
ownershipEntityRefs: refs,
|
||||
});
|
||||
await expect(loadIdentityOwnerRefs(identityApi)).resolves.toBe(refs);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -204,9 +140,12 @@ describe('useEntityOwnership', () => {
|
||||
});
|
||||
|
||||
describe('useEntityOwnership', () => {
|
||||
it('matches ownership via token claims', async () => {
|
||||
mockIdentityApi.getUserId.mockReturnValue('foo');
|
||||
mockIdentityApi.getIdToken.mockResolvedValue(tokenUserAndGroupEnt);
|
||||
it('matches ownership via ownership entity refs', async () => {
|
||||
mockIdentityApi.getBackstageIdentity.mockResolvedValue({
|
||||
type: 'user',
|
||||
userEntityRef: 'user:default/user1',
|
||||
ownershipEntityRefs: ['user:default/user1', 'group:default/group1'],
|
||||
});
|
||||
mockCatalogApi.getEntityByName.mockResolvedValue(undefined);
|
||||
|
||||
const { result, waitForValueToChange } = renderHook(
|
||||
@@ -224,32 +163,5 @@ describe('useEntityOwnership', () => {
|
||||
expect(result.current.loading).toBe(false);
|
||||
expect(result.current.isOwnedEntity(ownedEntity)).toBe(true);
|
||||
});
|
||||
|
||||
it('matches ownership via catalog user entity', async () => {
|
||||
mockIdentityApi.getUserId.mockReturnValue('user2');
|
||||
mockIdentityApi.getIdToken.mockResolvedValue(undefined);
|
||||
mockCatalogApi.getEntityByName.mockResolvedValue(user2Entity);
|
||||
|
||||
const { result, waitForValueToChange } = renderHook(
|
||||
() => useEntityOwnership(),
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.current.loading).toBe(true);
|
||||
expect(result.current.isOwnedEntity(ownedEntity)).toBe(false);
|
||||
|
||||
await waitForValueToChange(() => result.current.loading);
|
||||
|
||||
expect(result.current.loading).toBe(false);
|
||||
expect(result.current.isOwnedEntity(ownedEntity)).toBe(true);
|
||||
|
||||
expect(mockCatalogApi.getEntityByName).toBeCalledWith({
|
||||
kind: 'user',
|
||||
namespace: 'default',
|
||||
name: 'user2',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -28,33 +28,18 @@ import {
|
||||
identityApiRef,
|
||||
useApi,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import jwtDecoder from 'jwt-decode';
|
||||
import { useMemo } from 'react';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
import { catalogApiRef } from '../api';
|
||||
import { getEntityRelations } from '../utils/getEntityRelations';
|
||||
|
||||
// Takes a user ID from the identity, which can be on basically any form, and
|
||||
// returns an entity ref. E.g. if the input is "foo", it returns
|
||||
// "user:default/foo" to make sure it's a full ref.
|
||||
function extendUserId(id: string): string {
|
||||
try {
|
||||
const ref = parseEntityRef(id, {
|
||||
defaultKind: 'User',
|
||||
defaultNamespace: 'default',
|
||||
});
|
||||
return stringifyEntityRef(ref);
|
||||
} catch {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the relevant parts of the Backstage identity, and translates them into
|
||||
* a list of entity refs on string form that represent the user's ownership
|
||||
* connections.
|
||||
*
|
||||
* @public
|
||||
* @deprecated Use `ownershipEntityRefs` from `identityApi.getBackstageIdentity()` instead.
|
||||
*
|
||||
* @param identityApi - The IdentityApi implementation
|
||||
* @returns IdentityOwner refs as a string array
|
||||
@@ -62,30 +47,8 @@ function extendUserId(id: string): string {
|
||||
export async function loadIdentityOwnerRefs(
|
||||
identityApi: IdentityApi,
|
||||
): Promise<string[]> {
|
||||
const id = identityApi.getUserId();
|
||||
const token = await identityApi.getIdToken();
|
||||
const result: string[] = [];
|
||||
|
||||
if (id) {
|
||||
result.push(extendUserId(id));
|
||||
}
|
||||
|
||||
if (token) {
|
||||
try {
|
||||
const decoded = jwtDecoder(token) as any;
|
||||
if (decoded?.ent) {
|
||||
[decoded.ent]
|
||||
.flat()
|
||||
.filter(x => typeof x === 'string')
|
||||
.map(x => x.toLocaleLowerCase('en-US'))
|
||||
.forEach(x => result.push(x));
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
const identity = await identityApi.getBackstageIdentity();
|
||||
return identity.ownershipEntityRefs;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,9 +105,12 @@ export function useEntityOwnership(): {
|
||||
|
||||
// Trigger load only on mount
|
||||
const { loading, value: refs } = useAsync(async () => {
|
||||
const identityRefs = await loadIdentityOwnerRefs(identityApi);
|
||||
const catalogRefs = await loadCatalogOwnerRefs(catalogApi, identityRefs);
|
||||
return new Set([...identityRefs, ...catalogRefs]);
|
||||
const { ownershipEntityRefs } = await identityApi.getBackstageIdentity();
|
||||
const catalogRefs = await loadCatalogOwnerRefs(
|
||||
catalogApi,
|
||||
ownershipEntityRefs,
|
||||
);
|
||||
return new Set([...ownershipEntityRefs, ...catalogRefs]);
|
||||
}, []);
|
||||
|
||||
const isOwnedEntity = useMemo(() => {
|
||||
|
||||
@@ -14,7 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { UserEntity } from '@backstage/catalog-model';
|
||||
import {
|
||||
ENTITY_DEFAULT_NAMESPACE,
|
||||
parseEntityRef,
|
||||
UserEntity,
|
||||
} from '@backstage/catalog-model';
|
||||
import useAsync, { AsyncState } from 'react-use/lib/useAsync';
|
||||
import { catalogApiRef } from '../api';
|
||||
import { identityApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
@@ -26,15 +30,13 @@ export function useOwnUser(): AsyncState<UserEntity | undefined> {
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
const identityApi = useApi(identityApiRef);
|
||||
|
||||
// TODO: get the full entity (or at least the full entity name) from the
|
||||
// identityApi
|
||||
return useAsync(
|
||||
() =>
|
||||
catalogApi.getEntityByName({
|
||||
kind: 'User',
|
||||
namespace: 'default',
|
||||
name: identityApi.getUserId(),
|
||||
}) as Promise<UserEntity | undefined>,
|
||||
[catalogApi, identityApi],
|
||||
);
|
||||
return useAsync(async () => {
|
||||
const identity = await identityApi.getBackstageIdentity();
|
||||
return catalogApi.getEntityByName(
|
||||
parseEntityRef(identity.userEntityRef, {
|
||||
defaultKind: 'User',
|
||||
defaultNamespace: ENTITY_DEFAULT_NAMESPACE,
|
||||
}),
|
||||
) as Promise<UserEntity | undefined>;
|
||||
}, [catalogApi, identityApi]);
|
||||
}
|
||||
|
||||
@@ -28,38 +28,16 @@ const discoveryApi: DiscoveryApi = {
|
||||
},
|
||||
};
|
||||
const identityApi: IdentityApi = {
|
||||
getUserId() {
|
||||
return 'jane-fonda';
|
||||
},
|
||||
getProfile() {
|
||||
return { email: 'jane-fonda@spotify.com' };
|
||||
},
|
||||
async getIdToken() {
|
||||
return Promise.resolve('fake-id-token');
|
||||
},
|
||||
async signOut() {
|
||||
return Promise.resolve();
|
||||
},
|
||||
signOut: jest.fn(),
|
||||
getProfileInfo: jest.fn(),
|
||||
getBackstageIdentity: jest.fn(),
|
||||
getCredentials: jest.fn(),
|
||||
getCredentials: jest.fn().mockResolvedValue({ token: 'fake-id-token' }),
|
||||
};
|
||||
const guestIdentityApi: IdentityApi = {
|
||||
getUserId() {
|
||||
return 'guest';
|
||||
},
|
||||
getProfile() {
|
||||
return {};
|
||||
},
|
||||
async getIdToken() {
|
||||
return Promise.resolve(undefined);
|
||||
},
|
||||
async signOut() {
|
||||
return Promise.resolve();
|
||||
},
|
||||
signOut: jest.fn(),
|
||||
getProfileInfo: jest.fn(),
|
||||
getBackstageIdentity: jest.fn(),
|
||||
getCredentials: jest.fn(),
|
||||
getCredentials: jest.fn().mockResolvedValue({ token: undefined }),
|
||||
};
|
||||
|
||||
describe('CatalogClientWrapper', () => {
|
||||
|
||||
@@ -51,89 +51,108 @@ export class CatalogClientWrapper implements CatalogApi {
|
||||
id: string,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<Location | undefined> {
|
||||
return await this.client.getLocationById(id, {
|
||||
token: options?.token ?? (await this.identityApi.getIdToken()),
|
||||
});
|
||||
return await this.client.getLocationById(
|
||||
id,
|
||||
await this.getCredentials(options),
|
||||
);
|
||||
}
|
||||
|
||||
async getEntities(
|
||||
request?: CatalogEntitiesRequest,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<CatalogListResponse<Entity>> {
|
||||
return await this.client.getEntities(request, {
|
||||
token: options?.token ?? (await this.identityApi.getIdToken()),
|
||||
});
|
||||
return await this.client.getEntities(
|
||||
request,
|
||||
await this.getCredentials(options),
|
||||
);
|
||||
}
|
||||
|
||||
async getEntityByName(
|
||||
compoundName: EntityName,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<Entity | undefined> {
|
||||
return await this.client.getEntityByName(compoundName, {
|
||||
token: options?.token ?? (await this.identityApi.getIdToken()),
|
||||
});
|
||||
return await this.client.getEntityByName(
|
||||
compoundName,
|
||||
await this.getCredentials(options),
|
||||
);
|
||||
}
|
||||
|
||||
async addLocation(
|
||||
request: AddLocationRequest,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<AddLocationResponse> {
|
||||
return await this.client.addLocation(request, {
|
||||
token: options?.token ?? (await this.identityApi.getIdToken()),
|
||||
});
|
||||
return await this.client.addLocation(
|
||||
request,
|
||||
await this.getCredentials(options),
|
||||
);
|
||||
}
|
||||
|
||||
async getOriginLocationByEntity(
|
||||
entity: Entity,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<Location | undefined> {
|
||||
return await this.client.getOriginLocationByEntity(entity, {
|
||||
token: options?.token ?? (await this.identityApi.getIdToken()),
|
||||
});
|
||||
return await this.client.getOriginLocationByEntity(
|
||||
entity,
|
||||
await this.getCredentials(options),
|
||||
);
|
||||
}
|
||||
|
||||
async getLocationByEntity(
|
||||
entity: Entity,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<Location | undefined> {
|
||||
return await this.client.getLocationByEntity(entity, {
|
||||
token: options?.token ?? (await this.identityApi.getIdToken()),
|
||||
});
|
||||
return await this.client.getLocationByEntity(
|
||||
entity,
|
||||
await this.getCredentials(options),
|
||||
);
|
||||
}
|
||||
|
||||
async removeLocationById(
|
||||
id: string,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<void> {
|
||||
return await this.client.removeLocationById(id, {
|
||||
token: options?.token ?? (await this.identityApi.getIdToken()),
|
||||
});
|
||||
return await this.client.removeLocationById(
|
||||
id,
|
||||
await this.getCredentials(options),
|
||||
);
|
||||
}
|
||||
|
||||
async removeEntityByUid(
|
||||
uid: string,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<void> {
|
||||
return await this.client.removeEntityByUid(uid, {
|
||||
token: options?.token ?? (await this.identityApi.getIdToken()),
|
||||
});
|
||||
return await this.client.removeEntityByUid(
|
||||
uid,
|
||||
await this.getCredentials(options),
|
||||
);
|
||||
}
|
||||
|
||||
async refreshEntity(
|
||||
entityRef: string,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<void> {
|
||||
return await this.client.refreshEntity(entityRef, {
|
||||
token: options?.token ?? (await this.identityApi.getIdToken()),
|
||||
});
|
||||
return await this.client.refreshEntity(
|
||||
entityRef,
|
||||
await this.getCredentials(options),
|
||||
);
|
||||
}
|
||||
|
||||
async getEntityAncestors(
|
||||
request: CatalogEntityAncestorsRequest,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<CatalogEntityAncestorsResponse> {
|
||||
return await this.client.getEntityAncestors(request, {
|
||||
token: options?.token ?? (await this.identityApi.getIdToken()),
|
||||
});
|
||||
return await this.client.getEntityAncestors(
|
||||
request,
|
||||
await this.getCredentials(options),
|
||||
);
|
||||
}
|
||||
|
||||
private async getCredentials(
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<{ token?: string }> {
|
||||
if (options?.token) {
|
||||
return { token: options?.token };
|
||||
}
|
||||
return this.identityApi.getCredentials();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,9 +120,13 @@ describe('DefaultCatalogPage', () => {
|
||||
displayName: 'Display Name',
|
||||
};
|
||||
const identityApi: Partial<IdentityApi> = {
|
||||
getUserId: () => 'tools',
|
||||
getIdToken: async () => undefined,
|
||||
getProfile: () => testProfile,
|
||||
getBackstageIdentity: async () => ({
|
||||
type: 'user',
|
||||
userEntityRef: 'user:default/guest',
|
||||
ownershipEntityRefs: ['user:default/guest', 'group:default/tools'],
|
||||
}),
|
||||
getCredentials: async () => ({ token: undefined }),
|
||||
getProfileInfo: async () => testProfile,
|
||||
};
|
||||
const storageApi = MockStorageApi.create();
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.9.9",
|
||||
"@backstage/config": "^0.1.12",
|
||||
"@backstage/core-components": "^0.8.4",
|
||||
"@backstage/core-plugin-api": "^0.5.0",
|
||||
|
||||
@@ -23,7 +23,7 @@ import { IdentityApi, identityApiRef } from '@backstage/core-plugin-api';
|
||||
|
||||
describe('<CostInsightsHeader/>', () => {
|
||||
const identityApi: Partial<IdentityApi> = {
|
||||
getProfile: () => ({
|
||||
getProfileInfo: async () => ({
|
||||
email: 'test-email@example.com',
|
||||
displayName: 'User 1',
|
||||
}),
|
||||
|
||||
@@ -16,16 +16,15 @@
|
||||
|
||||
import React from 'react';
|
||||
import { Typography } from '@material-ui/core';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
import { useCostInsightsStyles } from '../../utils/styles';
|
||||
import { Group } from '../../types';
|
||||
import {
|
||||
identityApiRef,
|
||||
ProfileInfo,
|
||||
useApi,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { identityApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
|
||||
function name(profile: ProfileInfo | undefined): string {
|
||||
return profile?.displayName || 'Mysterious Stranger';
|
||||
function useDisplayName(): string {
|
||||
const identityApi = useApi(identityApiRef);
|
||||
const state = useAsync(() => identityApi.getProfileInfo(), [identityApi]);
|
||||
return state.loading ? '' : state.value?.displayName || 'Mysterious Stranger';
|
||||
}
|
||||
|
||||
type CostInsightsHeaderProps = {
|
||||
@@ -39,7 +38,7 @@ const CostInsightsHeaderNoData = ({
|
||||
owner,
|
||||
groups,
|
||||
}: CostInsightsHeaderProps) => {
|
||||
const profile = useApi(identityApiRef).getProfile();
|
||||
const displayName = useDisplayName();
|
||||
const classes = useCostInsightsStyles();
|
||||
const hasMultipleGroups = groups.length > 1;
|
||||
|
||||
@@ -52,8 +51,8 @@ const CostInsightsHeaderNoData = ({
|
||||
Well this is awkward
|
||||
</Typography>
|
||||
<Typography className={classes.h6Subtle} align="center" gutterBottom>
|
||||
<b>Hey, {name(profile)}!</b> <b>{owner}</b> doesn't seem to have any
|
||||
cloud costs.
|
||||
<b>Hey, {displayName}!</b> <b>{owner}</b> doesn't seem to have any cloud
|
||||
costs.
|
||||
</Typography>
|
||||
{hasMultipleGroups && (
|
||||
<Typography align="center" gutterBottom>
|
||||
@@ -68,7 +67,7 @@ const CostInsightsHeaderAlerts = ({
|
||||
owner,
|
||||
alerts,
|
||||
}: CostInsightsHeaderProps) => {
|
||||
const profile = useApi(identityApiRef).getProfile();
|
||||
const displayName = useDisplayName();
|
||||
const classes = useCostInsightsStyles();
|
||||
|
||||
return (
|
||||
@@ -80,7 +79,7 @@ const CostInsightsHeaderAlerts = ({
|
||||
You have {alerts} thing{alerts > 1 && 's'} to look into
|
||||
</Typography>
|
||||
<Typography className={classes.h6Subtle} align="center" gutterBottom>
|
||||
<b>Hey, {name(profile)}!</b> We've identified{' '}
|
||||
<b>Hey, {displayName}!</b> We've identified{' '}
|
||||
{alerts > 1 ? 'a few things ' : 'one thing '}
|
||||
<b>{owner}</b> should look into next.
|
||||
</Typography>
|
||||
@@ -89,7 +88,7 @@ const CostInsightsHeaderAlerts = ({
|
||||
};
|
||||
|
||||
const CostInsightsHeaderNoAlerts = ({ owner }: CostInsightsHeaderProps) => {
|
||||
const profile = useApi(identityApiRef).getProfile();
|
||||
const displayName = useDisplayName();
|
||||
const classes = useCostInsightsStyles();
|
||||
|
||||
return (
|
||||
@@ -101,7 +100,7 @@ const CostInsightsHeaderNoAlerts = ({ owner }: CostInsightsHeaderProps) => {
|
||||
Your team is doing great
|
||||
</Typography>
|
||||
<Typography className={classes.h6Subtle} align="center" gutterBottom>
|
||||
<b>Hey, {name(profile)}!</b> <b>{owner}</b> is doing well. No major
|
||||
<b>Hey, {displayName}!</b> <b>{owner}</b> is doing well. No major
|
||||
changes this month.
|
||||
</Typography>
|
||||
</>
|
||||
@@ -109,7 +108,7 @@ const CostInsightsHeaderNoAlerts = ({ owner }: CostInsightsHeaderProps) => {
|
||||
};
|
||||
|
||||
export const CostInsightsHeaderNoGroups = () => {
|
||||
const profile = useApi(identityApiRef).getProfile();
|
||||
const displayName = useDisplayName();
|
||||
const classes = useCostInsightsStyles();
|
||||
return (
|
||||
<>
|
||||
@@ -120,8 +119,7 @@ export const CostInsightsHeaderNoGroups = () => {
|
||||
Well this is awkward
|
||||
</Typography>
|
||||
<Typography className={classes.h6Subtle} align="center" gutterBottom>
|
||||
<b>Hey, {name(profile)}!</b> It doesn't look like you belong to any
|
||||
teams.
|
||||
<b>Hey, {displayName}!</b> It doesn't look like you belong to any teams.
|
||||
</Typography>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -26,6 +26,10 @@ import { MapLoadingToProps, useLoading } from './useLoading';
|
||||
import { Group, Maybe } from '../types';
|
||||
import { DefaultLoadingAction } from '../utils/loading';
|
||||
import { useApi, identityApiRef } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
ENTITY_DEFAULT_NAMESPACE,
|
||||
parseEntityRef,
|
||||
} from '@backstage/catalog-model';
|
||||
|
||||
type GroupsProviderLoadingProps = {
|
||||
dispatchLoadingGroups: (isLoading: boolean) => void;
|
||||
@@ -47,7 +51,7 @@ export const GroupsContext = React.createContext<
|
||||
>(undefined);
|
||||
|
||||
export const GroupsProvider = ({ children }: PropsWithChildren<{}>) => {
|
||||
const userId = useApi(identityApiRef).getUserId();
|
||||
const identityApi = useApi(identityApiRef);
|
||||
const client = useApi(costInsightsApiRef);
|
||||
const [error, setError] = useState<Maybe<Error>>(null);
|
||||
const { dispatchLoadingGroups } = useLoading(mapLoadingToProps);
|
||||
@@ -59,6 +63,11 @@ export const GroupsProvider = ({ children }: PropsWithChildren<{}>) => {
|
||||
|
||||
async function getUserGroups() {
|
||||
try {
|
||||
const { userEntityRef } = await identityApi.getBackstageIdentity();
|
||||
const { name: userId } = parseEntityRef(userEntityRef, {
|
||||
defaultKind: 'User',
|
||||
defaultNamespace: ENTITY_DEFAULT_NAMESPACE,
|
||||
});
|
||||
const g = await client.getUserGroups(userId);
|
||||
setGroups(g);
|
||||
} catch (e) {
|
||||
@@ -69,7 +78,7 @@ export const GroupsProvider = ({ children }: PropsWithChildren<{}>) => {
|
||||
}
|
||||
|
||||
getUserGroups();
|
||||
}, [userId, client]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
}, [client]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
if (error) {
|
||||
return <Alert severity="error">{error.message}</Alert>;
|
||||
|
||||
@@ -25,8 +25,8 @@ import { UrlPatternDiscovery } from '@backstage/core-app-api';
|
||||
const server = setupServer();
|
||||
|
||||
const identityApi = {
|
||||
async getIdToken() {
|
||||
return Promise.resolve('fake-id-token');
|
||||
async getCredentials() {
|
||||
return { token: 'fake-id-token' };
|
||||
},
|
||||
} as IdentityApi;
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ export class FossaClient implements FossaApi {
|
||||
query: Record<string, any>,
|
||||
): Promise<T> {
|
||||
const apiUrl = `${await this.discoveryApi.getBaseUrl('proxy')}/fossa`;
|
||||
const idToken = await this.identityApi.getIdToken();
|
||||
const { token: idToken } = await this.identityApi.getCredentials();
|
||||
const response = await fetch(
|
||||
`${apiUrl}/${path}?${new URLSearchParams(query).toString()}`,
|
||||
{
|
||||
|
||||
@@ -27,6 +27,10 @@ import {
|
||||
identityApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { Progress, Link } from '@backstage/core-components';
|
||||
import {
|
||||
ENTITY_DEFAULT_NAMESPACE,
|
||||
parseEntityRef,
|
||||
} from '@backstage/catalog-model';
|
||||
|
||||
export const IncidentActionsMenu = ({
|
||||
incident,
|
||||
@@ -40,7 +44,6 @@ export const IncidentActionsMenu = ({
|
||||
const ilertApi = useApi(ilertApiRef);
|
||||
const alertApi = useApi(alertApiRef);
|
||||
const identityApi = useApi(identityApiRef);
|
||||
const userName = identityApi.getUserId();
|
||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||
const callback = onIncidentChanged || ((_: Incident): void => {});
|
||||
const setProcessing = setIsLoading || ((_: boolean): void => {});
|
||||
@@ -64,6 +67,12 @@ export const IncidentActionsMenu = ({
|
||||
try {
|
||||
handleCloseMenu();
|
||||
setProcessing(true);
|
||||
|
||||
const { userEntityRef } = await identityApi.getBackstageIdentity();
|
||||
const { name: userName } = parseEntityRef(userEntityRef, {
|
||||
defaultKind: 'User',
|
||||
defaultNamespace: ENTITY_DEFAULT_NAMESPACE,
|
||||
});
|
||||
const newIncident = await ilertApi.acceptIncident(incident, userName);
|
||||
alertApi.post({ message: 'Incident accepted.' });
|
||||
|
||||
@@ -79,6 +88,11 @@ export const IncidentActionsMenu = ({
|
||||
try {
|
||||
handleCloseMenu();
|
||||
setProcessing(true);
|
||||
const { userEntityRef } = await identityApi.getBackstageIdentity();
|
||||
const { name: userName } = parseEntityRef(userEntityRef, {
|
||||
defaultKind: 'User',
|
||||
defaultNamespace: ENTITY_DEFAULT_NAMESPACE,
|
||||
});
|
||||
const newIncident = await ilertApi.resolveIncident(incident, userName);
|
||||
alertApi.post({ message: 'Incident resolved.' });
|
||||
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import {
|
||||
ENTITY_DEFAULT_NAMESPACE,
|
||||
parseEntityRef,
|
||||
} from '@backstage/catalog-model';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import Alert from '@material-ui/lab/Alert';
|
||||
import Button from '@material-ui/core/Button';
|
||||
@@ -80,7 +84,6 @@ export const IncidentNewModal = ({
|
||||
const ilertApi = useApi(ilertApiRef);
|
||||
const alertApi = useApi(alertApiRef);
|
||||
const identityApi = useApi(identityApiRef);
|
||||
const userName = identityApi.getUserId();
|
||||
const source = window.location.toString();
|
||||
const classes = useStyles();
|
||||
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
|
||||
@@ -102,6 +105,11 @@ export const IncidentNewModal = ({
|
||||
setIsLoading(true);
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
const { userEntityRef } = await identityApi.getBackstageIdentity();
|
||||
const { name: userName } = parseEntityRef(userEntityRef, {
|
||||
defaultKind: 'User',
|
||||
defaultNamespace: ENTITY_DEFAULT_NAMESPACE,
|
||||
});
|
||||
await ilertApi.createIncident({
|
||||
integrationKey,
|
||||
summary,
|
||||
|
||||
@@ -31,7 +31,7 @@ export class KafkaBackendClient implements KafkaApi {
|
||||
|
||||
private async internalGet(path: string): Promise<any> {
|
||||
const url = `${await this.discoveryApi.getBaseUrl('kafka')}${path}`;
|
||||
const idToken = await this.identityApi.getIdToken();
|
||||
const { token: idToken } = await this.identityApi.getCredentials();
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
|
||||
@@ -56,7 +56,7 @@ export class KubernetesBackendClient implements KubernetesApi {
|
||||
requestBody: KubernetesRequestBody,
|
||||
): Promise<any> {
|
||||
const url = `${await this.discoveryApi.getBaseUrl('kubernetes')}${path}`;
|
||||
const idToken = await this.identityApi.getIdToken();
|
||||
const { token: idToken } = await this.identityApi.getCredentials();
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -79,7 +79,7 @@ export class KubernetesBackendClient implements KubernetesApi {
|
||||
}
|
||||
|
||||
async getClusters(): Promise<{ name: string; authProvider: string }[]> {
|
||||
const idToken = await this.identityApi.getIdToken();
|
||||
const { token: idToken } = await this.identityApi.getCredentials();
|
||||
const url = `${await this.discoveryApi.getBaseUrl('kubernetes')}/clusters`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
|
||||
@@ -22,17 +22,9 @@ import { EntityProvider } from '@backstage/plugin-catalog-react';
|
||||
import { TriggerButton } from './';
|
||||
|
||||
import { ApiProvider } from '@backstage/core-app-api';
|
||||
import {
|
||||
alertApiRef,
|
||||
IdentityApi,
|
||||
identityApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { alertApiRef } from '@backstage/core-plugin-api';
|
||||
|
||||
describe('TriggerButton', () => {
|
||||
const mockIdentityApi: Partial<IdentityApi> = {
|
||||
getUserId: () => 'guest@example.com',
|
||||
};
|
||||
|
||||
const mockTriggerAlarmFn = jest.fn();
|
||||
const mockPagerDutyApi = {
|
||||
triggerAlarm: mockTriggerAlarmFn,
|
||||
@@ -40,7 +32,6 @@ describe('TriggerButton', () => {
|
||||
|
||||
const apis = TestApiRegistry.from(
|
||||
[alertApiRef, {}],
|
||||
[identityApiRef, mockIdentityApi],
|
||||
[pagerDutyApiRef, mockPagerDutyApi],
|
||||
);
|
||||
|
||||
|
||||
@@ -30,7 +30,11 @@ import {
|
||||
|
||||
describe('TriggerDialog', () => {
|
||||
const mockIdentityApi: Partial<IdentityApi> = {
|
||||
getUserId: () => 'guest@example.com',
|
||||
getBackstageIdentity: async () => ({
|
||||
type: 'user',
|
||||
userEntityRef: 'user:default/guest',
|
||||
ownershipEntityRefs: [],
|
||||
}),
|
||||
};
|
||||
|
||||
const mockTriggerAlarmFn = jest.fn();
|
||||
@@ -89,7 +93,7 @@ describe('TriggerDialog', () => {
|
||||
entity!.metadata!.annotations!['pagerduty.com/integration-key'],
|
||||
source: window.location.toString(),
|
||||
description,
|
||||
userName: 'guest@example.com',
|
||||
userName: 'guest',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -34,6 +34,10 @@ import {
|
||||
alertApiRef,
|
||||
identityApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import {
|
||||
ENTITY_DEFAULT_NAMESPACE,
|
||||
parseEntityRef,
|
||||
} from '@backstage/catalog-model';
|
||||
|
||||
type Props = {
|
||||
showDialog: boolean;
|
||||
@@ -49,18 +53,23 @@ export const TriggerDialog = ({
|
||||
const { name, integrationKey } = usePagerdutyEntity();
|
||||
const alertApi = useApi(alertApiRef);
|
||||
const identityApi = useApi(identityApiRef);
|
||||
const userName = identityApi.getUserId();
|
||||
const api = useApi(pagerDutyApiRef);
|
||||
const [description, setDescription] = useState<string>('');
|
||||
|
||||
const [{ value, loading, error }, handleTriggerAlarm] = useAsyncFn(
|
||||
async (descriptions: string) =>
|
||||
async (descriptions: string) => {
|
||||
const { userEntityRef } = await identityApi.getBackstageIdentity();
|
||||
const { name: userName } = parseEntityRef(userEntityRef, {
|
||||
defaultKind: 'User',
|
||||
defaultNamespace: ENTITY_DEFAULT_NAMESPACE,
|
||||
});
|
||||
await api.triggerAlarm({
|
||||
integrationKey: integrationKey as string,
|
||||
source: window.location.toString(),
|
||||
description: descriptions,
|
||||
userName,
|
||||
}),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const descriptionChanged = (
|
||||
@@ -73,7 +82,7 @@ export const TriggerDialog = ({
|
||||
if (value) {
|
||||
(async () => {
|
||||
alertApi.post({
|
||||
message: `Alarm successfully triggered by ${userName}`,
|
||||
message: `Alarm successfully triggered`,
|
||||
});
|
||||
|
||||
handleDialog();
|
||||
@@ -83,7 +92,7 @@ export const TriggerDialog = ({
|
||||
onIncidentCreated?.();
|
||||
})();
|
||||
}
|
||||
}, [value, alertApi, handleDialog, userName, onIncidentCreated]);
|
||||
}, [value, alertApi, handleDialog, onIncidentCreated]);
|
||||
|
||||
if (error) {
|
||||
alertApi.post({
|
||||
|
||||
@@ -45,9 +45,10 @@ export class IdentityPermissionApi implements PermissionApi {
|
||||
}
|
||||
|
||||
async authorize(request: AuthorizeQuery): Promise<AuthorizeDecision> {
|
||||
const response = await this.permissionClient.authorize([request], {
|
||||
token: await this.identityApi.getIdToken(),
|
||||
});
|
||||
const response = await this.permissionClient.authorize(
|
||||
[request],
|
||||
await this.identityApi.getCredentials(),
|
||||
);
|
||||
return response[0];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ export class RollbarClient implements RollbarApi {
|
||||
|
||||
private async get(path: string): Promise<any> {
|
||||
const url = `${await this.discoveryApi.getBaseUrl('rollbar')}${path}`;
|
||||
const idToken = await this.identityApi.getIdToken();
|
||||
const { token: idToken } = await this.identityApi.getCredentials();
|
||||
const response = await fetch(url, {
|
||||
headers: idToken ? { Authorization: `Bearer ${idToken}` } : {},
|
||||
});
|
||||
|
||||
@@ -124,7 +124,7 @@ export class ScaffolderClient implements ScaffolderApi {
|
||||
): Promise<TemplateParameterSchema> {
|
||||
const { namespace, kind, name } = templateName;
|
||||
|
||||
const token = await this.identityApi.getIdToken();
|
||||
const { token } = await this.identityApi.getCredentials();
|
||||
const baseUrl = await this.discoveryApi.getBaseUrl('scaffolder');
|
||||
const templatePath = [namespace, kind, name]
|
||||
.map(s => encodeURIComponent(s))
|
||||
@@ -156,7 +156,7 @@ export class ScaffolderClient implements ScaffolderApi {
|
||||
templateName: string,
|
||||
values: Record<string, any>,
|
||||
): Promise<string> {
|
||||
const token = await this.identityApi.getIdToken();
|
||||
const { token } = await this.identityApi.getCredentials();
|
||||
const url = `${await this.discoveryApi.getBaseUrl('scaffolder')}/v2/tasks`;
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
@@ -178,7 +178,7 @@ export class ScaffolderClient implements ScaffolderApi {
|
||||
}
|
||||
|
||||
async getTask(taskId: string) {
|
||||
const token = await this.identityApi.getIdToken();
|
||||
const { token } = await this.identityApi.getCredentials();
|
||||
const baseUrl = await this.discoveryApi.getBaseUrl('scaffolder');
|
||||
const url = `${baseUrl}/v2/tasks/${encodeURIComponent(taskId)}`;
|
||||
const response = await fetch(url, {
|
||||
@@ -295,7 +295,7 @@ export class ScaffolderClient implements ScaffolderApi {
|
||||
*/
|
||||
async listActions(): Promise<ListActionsResponse> {
|
||||
const baseUrl = await this.discoveryApi.getBaseUrl('scaffolder');
|
||||
const token = await this.identityApi.getIdToken();
|
||||
const { token } = await this.identityApi.getCredentials();
|
||||
const response = await fetch(`${baseUrl}/v2/actions`, {
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
});
|
||||
|
||||
@@ -27,16 +27,13 @@ describe('apis', () => {
|
||||
const getBaseUrl = jest.fn().mockResolvedValue(baseUrl);
|
||||
|
||||
const token = 'AUTHTOKEN';
|
||||
const withToken = jest.fn().mockResolvedValue(token);
|
||||
const withoutToken = jest.fn().mockResolvedValue(undefined);
|
||||
const createIdentityApiMock = (getIdToken: any) => ({
|
||||
getIdToken,
|
||||
getUserId: jest.fn(),
|
||||
getProfile: jest.fn(),
|
||||
const withToken = jest.fn().mockResolvedValue({ token });
|
||||
const withoutToken = jest.fn().mockResolvedValue({ token: undefined });
|
||||
const createIdentityApiMock = (getCredentials: any) => ({
|
||||
signOut: jest.fn(),
|
||||
getProfileInfo: jest.fn(),
|
||||
getBackstageIdentity: jest.fn(),
|
||||
getCredentials: jest.fn(),
|
||||
getCredentials,
|
||||
});
|
||||
|
||||
const client = new SearchClient({
|
||||
|
||||
@@ -44,7 +44,7 @@ export class SearchClient implements SearchApi {
|
||||
}
|
||||
|
||||
async query(query: SearchQuery): Promise<SearchResultSet> {
|
||||
const token = await this.identityApi.getIdToken();
|
||||
const { token } = await this.identityApi.getCredentials();
|
||||
const queryString = qs.stringify(query);
|
||||
const url = `${await this.discoveryApi.getBaseUrl(
|
||||
'search/query',
|
||||
|
||||
@@ -55,7 +55,7 @@ export class ProductionSentryApi implements SentryApi {
|
||||
if (!this.identityApi) {
|
||||
return {};
|
||||
}
|
||||
const token = await this.identityApi.getIdToken();
|
||||
const { token } = await this.identityApi.getCredentials();
|
||||
return {
|
||||
headers: {
|
||||
authorization: `Bearer ${token}`,
|
||||
|
||||
@@ -25,38 +25,16 @@ import { IdentityApi } from '@backstage/core-plugin-api';
|
||||
const server = setupServer();
|
||||
|
||||
const identityApiAuthenticated: IdentityApi = {
|
||||
getUserId() {
|
||||
return 'jane-fonda';
|
||||
},
|
||||
getProfile() {
|
||||
return { email: 'jane-fonda@spotify.com' };
|
||||
},
|
||||
async getIdToken() {
|
||||
return Promise.resolve('fake-id-token');
|
||||
},
|
||||
async signOut() {
|
||||
return Promise.resolve();
|
||||
},
|
||||
signOut: jest.fn(),
|
||||
getProfileInfo: jest.fn(),
|
||||
getBackstageIdentity: jest.fn(),
|
||||
getCredentials: jest.fn(),
|
||||
getCredentials: jest.fn().mockResolvedValue({ token: 'fake-id-token' }),
|
||||
};
|
||||
const identityApiGuest: IdentityApi = {
|
||||
getUserId() {
|
||||
return 'guest';
|
||||
},
|
||||
getProfile() {
|
||||
return {};
|
||||
},
|
||||
async getIdToken() {
|
||||
return Promise.resolve(undefined);
|
||||
},
|
||||
async signOut() {
|
||||
return Promise.resolve();
|
||||
},
|
||||
signOut: jest.fn(),
|
||||
getProfileInfo: jest.fn(),
|
||||
getBackstageIdentity: jest.fn(),
|
||||
getCredentials: jest.fn(),
|
||||
getCredentials: jest.fn().mockResolvedValue({ token: undefined }),
|
||||
};
|
||||
|
||||
describe('SonarQubeClient', () => {
|
||||
|
||||
@@ -42,7 +42,7 @@ export class SonarQubeClient implements SonarQubeApi {
|
||||
path: string,
|
||||
query: { [key in string]: any },
|
||||
): Promise<T | undefined> {
|
||||
const idToken = await this.identityApi.getIdToken();
|
||||
const { token: idToken } = await this.identityApi.getCredentials();
|
||||
|
||||
const apiUrl = `${await this.discoveryApi.getBaseUrl('proxy')}/sonarqube`;
|
||||
const response = await fetch(
|
||||
|
||||
@@ -20,24 +20,15 @@ import { TestApiRegistry, wrapInTestApp } from '@backstage/test-utils';
|
||||
import { splunkOnCallApiRef } from '../../api';
|
||||
import { MOCK_TEAM, MOCK_INCIDENT } from '../../api/mocks';
|
||||
|
||||
import {
|
||||
alertApiRef,
|
||||
IdentityApi,
|
||||
identityApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { alertApiRef } from '@backstage/core-plugin-api';
|
||||
import { ApiProvider } from '@backstage/core-app-api';
|
||||
|
||||
const mockIdentityApi: Partial<IdentityApi> = {
|
||||
getUserId: () => 'test',
|
||||
};
|
||||
|
||||
const mockSplunkOnCallApi = {
|
||||
getIncidents: jest.fn(),
|
||||
getTeams: jest.fn(),
|
||||
};
|
||||
const apis = TestApiRegistry.from(
|
||||
[alertApiRef, {}],
|
||||
[identityApiRef, mockIdentityApi],
|
||||
[splunkOnCallApiRef, mockSplunkOnCallApi],
|
||||
);
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ export class TechInsightsClient implements TechInsightsApi {
|
||||
|
||||
async getAllChecks(): Promise<Check[]> {
|
||||
const url = await this.discoveryApi.getBaseUrl('tech-insights');
|
||||
const token = await this.identityApi.getIdToken();
|
||||
const { token } = await this.identityApi.getCredentials();
|
||||
const response = await fetch(`${url}/checks`, {
|
||||
headers: token
|
||||
? {
|
||||
@@ -78,7 +78,7 @@ export class TechInsightsClient implements TechInsightsApi {
|
||||
checks?: Check[],
|
||||
): Promise<CheckResult[]> {
|
||||
const url = await this.discoveryApi.getBaseUrl('tech-insights');
|
||||
const token = await this.identityApi.getIdToken();
|
||||
const { token } = await this.identityApi.getCredentials();
|
||||
const { namespace, kind, name } = entityParams;
|
||||
const checkIds = checks ? checks.map(check => check.id) : [];
|
||||
const requestBody = { checks: checkIds.length > 0 ? checkIds : undefined };
|
||||
@@ -106,7 +106,7 @@ export class TechInsightsClient implements TechInsightsApi {
|
||||
checks?: Check[],
|
||||
): Promise<BulkCheckResponse> {
|
||||
const url = await this.discoveryApi.getBaseUrl('tech-insights');
|
||||
const token = await this.identityApi.getIdToken();
|
||||
const { token } = await this.identityApi.getCredentials();
|
||||
const checkIds = checks ? checks.map(check => check.id) : [];
|
||||
const requestBody = {
|
||||
entities,
|
||||
|
||||
@@ -40,9 +40,6 @@ describe('TechDocsStorageClient', () => {
|
||||
} as Partial<Config>;
|
||||
const discoveryApi = UrlPatternDiscovery.compile(mockBaseUrl);
|
||||
const identityApi: jest.Mocked<IdentityApi> = {
|
||||
getIdToken: jest.fn(),
|
||||
getProfile: jest.fn(),
|
||||
getUserId: jest.fn(),
|
||||
signOut: jest.fn(),
|
||||
getProfileInfo: jest.fn(),
|
||||
getBackstageIdentity: jest.fn(),
|
||||
@@ -51,6 +48,7 @@ describe('TechDocsStorageClient', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
identityApi.getCredentials.mockResolvedValue({ token: undefined });
|
||||
});
|
||||
|
||||
it('should return correct base url based on defined storage', async () => {
|
||||
@@ -122,7 +120,7 @@ describe('TechDocsStorageClient', () => {
|
||||
},
|
||||
);
|
||||
|
||||
identityApi.getIdToken.mockResolvedValue('token');
|
||||
identityApi.getCredentials.mockResolvedValue({ token: 'token' });
|
||||
|
||||
await storageApi.syncEntityDocs(mockEntity);
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ export class TechDocsClient implements TechDocsApi {
|
||||
|
||||
const apiOrigin = await this.getApiOrigin();
|
||||
const requestUrl = `${apiOrigin}/metadata/techdocs/${namespace}/${kind}/${name}`;
|
||||
const token = await this.identityApi.getIdToken();
|
||||
const { token } = await this.identityApi.getCredentials();
|
||||
|
||||
const request = await fetch(`${requestUrl}`, {
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
@@ -93,7 +93,7 @@ export class TechDocsClient implements TechDocsApi {
|
||||
|
||||
const apiOrigin = await this.getApiOrigin();
|
||||
const requestUrl = `${apiOrigin}/metadata/entity/${namespace}/${kind}/${name}`;
|
||||
const token = await this.identityApi.getIdToken();
|
||||
const { token } = await this.identityApi.getCredentials();
|
||||
|
||||
const request = await fetch(`${requestUrl}`, {
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
@@ -160,7 +160,7 @@ export class TechDocsStorageClient implements TechDocsStorageApi {
|
||||
|
||||
const storageUrl = await this.getStorageUrl();
|
||||
const url = `${storageUrl}/${namespace}/${kind}/${name}/${path}`;
|
||||
const token = await this.identityApi.getIdToken();
|
||||
const { token } = await this.identityApi.getCredentials();
|
||||
|
||||
const request = await fetch(
|
||||
`${url.endsWith('/') ? url : `${url}/`}index.html`,
|
||||
@@ -207,7 +207,7 @@ export class TechDocsStorageClient implements TechDocsStorageApi {
|
||||
|
||||
const apiOrigin = await this.getApiOrigin();
|
||||
const url = `${apiOrigin}/sync/${namespace}/${kind}/${name}`;
|
||||
const token = await this.identityApi.getIdToken();
|
||||
const { token } = await this.identityApi.getCredentials();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// Polyfill is used to add support for custom headers and auth
|
||||
|
||||
@@ -46,7 +46,7 @@ export class TodoClient implements TodoApi {
|
||||
async listTodos(options: TodoListOptions): Promise<TodoListResult> {
|
||||
const { entity, offset, limit, orderBy, filters } = options;
|
||||
const baseUrl = await this.discoveryApi.getBaseUrl('todo');
|
||||
const token = await this.identityApi.getIdToken();
|
||||
const { token } = await this.identityApi.getCredentials();
|
||||
|
||||
const query = new URLSearchParams();
|
||||
if (entity) {
|
||||
|
||||
Reference in New Issue
Block a user