Merge branch 'master' of github.com:spotify/backstage into mob/cookiecutter-templater
* 'master' of github.com:spotify/backstage: (21 commits) auth-backend: clean up comments + fix getting email from profile feat(catalog): show owner and lifecycle in header chore(catalog): move the entity examples to catalog-model plugins/graphiql: grayscale icon app: add graphiql plugin with gitlab and github endpoints dev-utils: add google and github auth APIs to default api factories plugins/graphiql: add GitHub endpoint with automatic scope detection build(deps): bump yup from 0.28.5 to 0.29.1 v0.1.1-alpha.12 packages: publish entire dist folder build(deps-dev): bump @storybook/addon-links from 5.3.18 to 5.3.19 (#1414) v0.1.1-alpha.11 cli: include assets-types in published package storybook: update identity api mock core-api: update loginPopup tests core: fix sign-in hot reloading issues and unmounted component access core-api: update web message response handling in loginPopup to expect RFC format Identity and profile work, wip build(deps): bump @rollup/plugin-node-resolve from 7.1.3 to 8.1.0 backend - fix types + refactoring ...
This commit is contained in:
+17
-16
@@ -1,23 +1,24 @@
|
||||
{
|
||||
"name": "example-app",
|
||||
"version": "0.1.1-alpha.10",
|
||||
"version": "0.1.1-alpha.12",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.10",
|
||||
"@backstage/core": "^0.1.1-alpha.10",
|
||||
"@backstage/plugin-catalog": "^0.1.1-alpha.10",
|
||||
"@backstage/plugin-circleci": "^0.1.1-alpha.10",
|
||||
"@backstage/plugin-explore": "^0.1.1-alpha.10",
|
||||
"@backstage/plugin-gitops-profiles": "^0.1.1-alpha.10",
|
||||
"@backstage/plugin-lighthouse": "^0.1.1-alpha.10",
|
||||
"@backstage/plugin-register-component": "^0.1.1-alpha.10",
|
||||
"@backstage/plugin-scaffolder": "^0.1.1-alpha.10",
|
||||
"@backstage/plugin-sentry": "^0.1.1-alpha.10",
|
||||
"@backstage/plugin-tech-radar": "^0.1.1-alpha.10",
|
||||
"@backstage/plugin-techdocs": "^0.1.1-alpha.10",
|
||||
"@backstage/plugin-welcome": "^0.1.1-alpha.10",
|
||||
"@backstage/test-utils": "^0.1.1-alpha.10",
|
||||
"@backstage/theme": "^0.1.1-alpha.10",
|
||||
"@backstage/cli": "^0.1.1-alpha.12",
|
||||
"@backstage/core": "^0.1.1-alpha.12",
|
||||
"@backstage/plugin-catalog": "^0.1.1-alpha.12",
|
||||
"@backstage/plugin-circleci": "^0.1.1-alpha.12",
|
||||
"@backstage/plugin-explore": "^0.1.1-alpha.12",
|
||||
"@backstage/plugin-gitops-profiles": "^0.1.1-alpha.12",
|
||||
"@backstage/plugin-graphiql": "^0.1.1-alpha.12",
|
||||
"@backstage/plugin-lighthouse": "^0.1.1-alpha.12",
|
||||
"@backstage/plugin-register-component": "^0.1.1-alpha.12",
|
||||
"@backstage/plugin-scaffolder": "^0.1.1-alpha.12",
|
||||
"@backstage/plugin-sentry": "^0.1.1-alpha.12",
|
||||
"@backstage/plugin-tech-radar": "^0.1.1-alpha.12",
|
||||
"@backstage/plugin-techdocs": "^0.1.1-alpha.12",
|
||||
"@backstage/plugin-welcome": "^0.1.1-alpha.12",
|
||||
"@backstage/test-utils": "^0.1.1-alpha.12",
|
||||
"@backstage/theme": "^0.1.1-alpha.12",
|
||||
"@material-ui/core": "^4.9.1",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"prop-types": "^15.7.2",
|
||||
|
||||
@@ -45,6 +45,10 @@ import { CircleCIApi, circleCIApiRef } from '@backstage/plugin-circleci';
|
||||
import { catalogApiRef, CatalogClient } from '@backstage/plugin-catalog';
|
||||
|
||||
import { gitOpsApiRef, GitOpsRestApi } from '@backstage/plugin-gitops-profiles';
|
||||
import {
|
||||
graphQlBrowseApiRef,
|
||||
GraphQLEndpoints,
|
||||
} from '@backstage/plugin-graphiql';
|
||||
|
||||
export const apis = (config: ConfigApi) => {
|
||||
// eslint-disable-next-line no-console
|
||||
@@ -78,7 +82,7 @@ export const apis = (config: ConfigApi) => {
|
||||
}),
|
||||
);
|
||||
|
||||
builder.add(
|
||||
const githubAuthApi = builder.add(
|
||||
githubAuthApiRef,
|
||||
GithubAuth.create({
|
||||
apiOrigin: 'http://localhost:7000',
|
||||
@@ -105,5 +109,22 @@ export const apis = (config: ConfigApi) => {
|
||||
|
||||
builder.add(gitOpsApiRef, new GitOpsRestApi('http://localhost:3008'));
|
||||
|
||||
builder.add(
|
||||
graphQlBrowseApiRef,
|
||||
GraphQLEndpoints.from([
|
||||
GraphQLEndpoints.create({
|
||||
id: 'gitlab',
|
||||
title: 'GitLab',
|
||||
url: 'https://gitlab.com/api/graphql',
|
||||
}),
|
||||
GraphQLEndpoints.github({
|
||||
id: 'github',
|
||||
title: 'GitHub',
|
||||
errorApi,
|
||||
githubAuthApi,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
return builder.build();
|
||||
};
|
||||
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
SidebarPinButton,
|
||||
} from '@backstage/core';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { graphiQLRouteRef } from '@backstage/plugin-graphiql';
|
||||
|
||||
const useSidebarLogoStyles = makeStyles({
|
||||
root: {
|
||||
@@ -94,6 +95,11 @@ const Root: FC<{}> = ({ children }) => (
|
||||
<SidebarItem icon={MapIcon} to="tech-radar" text="Tech Radar" />
|
||||
<SidebarItem icon={RuleIcon} to="lighthouse" text="Lighthouse" />
|
||||
<SidebarItem icon={BuildIcon} to="circleci" text="CircleCI" />
|
||||
<SidebarItem
|
||||
icon={graphiQLRouteRef.icon!}
|
||||
to={graphiQLRouteRef.path}
|
||||
text={graphiQLRouteRef.title}
|
||||
/>
|
||||
<SidebarSpace />
|
||||
<SidebarDivider />
|
||||
<SidebarThemeToggle />
|
||||
|
||||
@@ -24,3 +24,4 @@ export { plugin as RegisterComponent } from '@backstage/plugin-register-componen
|
||||
export { plugin as Sentry } from '@backstage/plugin-sentry';
|
||||
export { plugin as GitopsProfiles } from '@backstage/plugin-gitops-profiles';
|
||||
export { plugin as TechDocs } from '@backstage/plugin-techdocs';
|
||||
export { plugin as GraphiQL } from '@backstage/plugin-graphiql';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/backend-common",
|
||||
"description": "Common functionality library for Backstage backends",
|
||||
"version": "0.1.1-alpha.10",
|
||||
"version": "0.1.1-alpha.12",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"private": false,
|
||||
@@ -29,7 +29,7 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/config": "^0.1.1-alpha.10",
|
||||
"@backstage/config": "^0.1.1-alpha.12",
|
||||
"@types/cors": "^2.8.6",
|
||||
"@types/express": "^4.17.6",
|
||||
"compression": "^1.7.4",
|
||||
@@ -41,7 +41,7 @@
|
||||
"winston": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.10",
|
||||
"@backstage/cli": "^0.1.1-alpha.12",
|
||||
"@types/compression": "^1.7.0",
|
||||
"@types/http-errors": "^1.6.3",
|
||||
"@types/morgan": "^1.9.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "example-backend",
|
||||
"version": "0.1.1-alpha.10",
|
||||
"version": "0.1.1-alpha.12",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "src/index.ts",
|
||||
"private": true,
|
||||
@@ -17,15 +17,15 @@
|
||||
"migrate:create": "knex migrate:make -x ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.1.1-alpha.10",
|
||||
"@backstage/catalog-model": "^0.1.1-alpha.10",
|
||||
"@backstage/config": "^0.1.1-alpha.10",
|
||||
"@backstage/config-loader": "^0.1.1-alpha.10",
|
||||
"@backstage/plugin-auth-backend": "^0.1.1-alpha.10",
|
||||
"@backstage/plugin-catalog-backend": "^0.1.1-alpha.10",
|
||||
"@backstage/plugin-identity-backend": "^0.1.1-alpha.10",
|
||||
"@backstage/plugin-scaffolder-backend": "^0.1.1-alpha.10",
|
||||
"@backstage/plugin-sentry-backend": "^0.1.1-alpha.10",
|
||||
"@backstage/backend-common": "^0.1.1-alpha.12",
|
||||
"@backstage/catalog-model": "^0.1.1-alpha.12",
|
||||
"@backstage/config": "^0.1.1-alpha.12",
|
||||
"@backstage/config-loader": "^0.1.1-alpha.12",
|
||||
"@backstage/plugin-auth-backend": "^0.1.1-alpha.12",
|
||||
"@backstage/plugin-catalog-backend": "^0.1.1-alpha.12",
|
||||
"@backstage/plugin-identity-backend": "^0.1.1-alpha.12",
|
||||
"@backstage/plugin-scaffolder-backend": "^0.1.1-alpha.12",
|
||||
"@backstage/plugin-sentry-backend": "^0.1.1-alpha.12",
|
||||
"dockerode": "^3.2.0",
|
||||
"express": "^4.17.1",
|
||||
"knex": "^0.21.1",
|
||||
@@ -33,10 +33,10 @@
|
||||
"winston": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.10",
|
||||
"@types/dockerode": "^2.5.32",
|
||||
"@backstage/cli": "^0.1.1-alpha.12",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/express-serve-static-core": "^4.17.5",
|
||||
"@types/helmet": "^0.0.47"
|
||||
"@types/helmet": "^0.0.47",
|
||||
"@types/dockerode": "^2.5.32"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: artist-lookup
|
||||
description: Artist Lookup
|
||||
spec:
|
||||
type: service
|
||||
lifecycle: experimental
|
||||
owner: tools@example.com
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: playback-sdk
|
||||
description: Audio and video playback SDK
|
||||
spec:
|
||||
type: library
|
||||
lifecycle: experimental
|
||||
owner: players@example.com
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: playback-order
|
||||
description: Playback Order
|
||||
spec:
|
||||
type: service
|
||||
lifecycle: production
|
||||
owner: tools@example.com
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: podcast-api
|
||||
description: Podcast API
|
||||
spec:
|
||||
type: service
|
||||
lifecycle: experimental
|
||||
owner: tools@example.com
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: queue-proxy
|
||||
description: Queue Proxy
|
||||
spec:
|
||||
type: website
|
||||
lifecycle: production
|
||||
owner: tools@example.com
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: searcher
|
||||
description: Searcher
|
||||
spec:
|
||||
type: service
|
||||
lifecycle: production
|
||||
owner: tools@example.com
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: shuffle-api
|
||||
description: Shuffle API
|
||||
spec:
|
||||
type: service
|
||||
lifecycle: production
|
||||
owner: tools@example.com
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: www-artist
|
||||
description: Artist main website
|
||||
spec:
|
||||
type: website
|
||||
lifecycle: production
|
||||
owner: artists@example.com
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/catalog-model",
|
||||
"version": "0.1.1-alpha.10",
|
||||
"version": "0.1.1-alpha.12",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -20,20 +20,20 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/config": "^0.1.1-alpha.10",
|
||||
"@backstage/config": "^0.1.1-alpha.12",
|
||||
"@types/yup": "^0.28.2",
|
||||
"lodash": "^4.17.15",
|
||||
"uuid": "^8.0.0",
|
||||
"yup": "^0.28.5"
|
||||
"yup": "^0.29.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.10",
|
||||
"@backstage/cli": "^0.1.1-alpha.12",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/jest": "^25.2.2",
|
||||
"@types/lodash": "^4.14.151",
|
||||
"yaml": "^1.9.2"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*.{js,d.ts}"
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/cli",
|
||||
"description": "CLI for developing Backstage plugins and apps",
|
||||
"version": "0.1.1-alpha.10",
|
||||
"version": "0.1.1-alpha.12",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
@@ -29,14 +29,14 @@
|
||||
"backstage-cli": "bin/backstage-cli"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/config": "^0.1.1-alpha.10",
|
||||
"@backstage/config-loader": "^0.1.1-alpha.10",
|
||||
"@backstage/config": "^0.1.1-alpha.12",
|
||||
"@backstage/config-loader": "^0.1.1-alpha.12",
|
||||
"@hot-loader/react-dom": "^16.13.0",
|
||||
"@lerna/package-graph": "^3.18.5",
|
||||
"@lerna/project": "^3.18.0",
|
||||
"@rollup/plugin-commonjs": "^12.0.0",
|
||||
"@rollup/plugin-commonjs": "^13.0.0",
|
||||
"@rollup/plugin-json": "^4.0.2",
|
||||
"@rollup/plugin-node-resolve": "^7.1.1",
|
||||
"@rollup/plugin-node-resolve": "^8.1.0",
|
||||
"@spotify/eslint-config": "^7.0.1",
|
||||
"@sucrase/webpack-loader": "^2.0.0",
|
||||
"@svgr/plugin-jsx": "4.3.x",
|
||||
@@ -116,6 +116,7 @@
|
||||
"zombie": "^6.1.4"
|
||||
},
|
||||
"files": [
|
||||
"asset-types",
|
||||
"templates",
|
||||
"config",
|
||||
"bin",
|
||||
|
||||
@@ -37,6 +37,6 @@
|
||||
"jest-fetch-mock": "^3.0.3"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*.{js,d.ts}"
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -42,6 +42,6 @@
|
||||
"jest-fetch-mock": "^3.0.3"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*.{js,d.ts}"
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/config-loader",
|
||||
"description": "Config loading functionality used by Backstage backend, and CLI",
|
||||
"version": "0.1.1-alpha.10",
|
||||
"version": "0.1.1-alpha.12",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -30,10 +30,10 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/config": "^0.1.1-alpha.10",
|
||||
"@backstage/config": "^0.1.1-alpha.12",
|
||||
"fs-extra": "^9.0.0",
|
||||
"yaml": "^1.9.2",
|
||||
"yup": "^0.28.5"
|
||||
"yup": "^0.29.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^25.2.2",
|
||||
@@ -41,6 +41,6 @@
|
||||
"@types/yup": "^0.28.2"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*.{js,d.ts}"
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/config",
|
||||
"description": "Config API used by Backstage core, backend, and CLI",
|
||||
"version": "0.1.1-alpha.10",
|
||||
"version": "0.1.1-alpha.12",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -37,6 +37,6 @@
|
||||
"@types/node": "^12.0.0"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*.{js,d.ts}"
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/core-api",
|
||||
"description": "Internal Core API used by Backstage plugins and apps",
|
||||
"version": "0.1.1-alpha.10",
|
||||
"version": "0.1.1-alpha.12",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -29,8 +29,8 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/config": "^0.1.1-alpha.10",
|
||||
"@backstage/theme": "^0.1.1-alpha.10",
|
||||
"@backstage/config": "^0.1.1-alpha.12",
|
||||
"@backstage/theme": "^0.1.1-alpha.12",
|
||||
"@material-ui/core": "^4.9.1",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@types/react": "^16.9",
|
||||
@@ -41,8 +41,8 @@
|
||||
"zen-observable": "^0.8.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.10",
|
||||
"@backstage/test-utils-core": "^0.1.1-alpha.10",
|
||||
"@backstage/cli": "^0.1.1-alpha.12",
|
||||
"@backstage/test-utils-core": "^0.1.1-alpha.12",
|
||||
"@testing-library/jest-dom": "^5.7.0",
|
||||
"@testing-library/react": "^9.3.2",
|
||||
"@testing-library/user-event": "^10.2.4",
|
||||
@@ -52,6 +52,6 @@
|
||||
"jest-fetch-mock": "^3.0.3"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*.{js,d.ts}"
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { createApiRef } from '../ApiRef';
|
||||
import { ProfileInfo } from './auth';
|
||||
|
||||
/**
|
||||
* The Identity API used to identify and get information about the signed in user.
|
||||
@@ -29,6 +30,11 @@ export type IdentityApi = {
|
||||
*/
|
||||
getUserId(): string;
|
||||
|
||||
/**
|
||||
* The profile of the signed in user.
|
||||
*/
|
||||
getProfile(): ProfileInfo;
|
||||
|
||||
/**
|
||||
* An OpenID Connect ID Token which proves the identity of the signed in user.
|
||||
*
|
||||
|
||||
@@ -37,13 +37,13 @@ import { Observable } from '../..';
|
||||
*/
|
||||
export type OAuthScope = string | string[];
|
||||
|
||||
export type AccessTokenOptions = {
|
||||
export type AuthRequestOptions = {
|
||||
/**
|
||||
* If this is set to true, the user will not be prompted to log in,
|
||||
* and an empty access token will be returned if there is no existing session.
|
||||
* and an empty response will be returned if there is no existing session.
|
||||
*
|
||||
* This can be used to perform a check whether the user is logged in with a set of scopes,
|
||||
* or if you don't want to force a user to be logged in, but provide functionality if they already are.
|
||||
* This can be used to perform a check whether the user is logged in, or if you don't
|
||||
* want to force a user to be logged in, but provide functionality if they already are.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
@@ -88,7 +88,7 @@ export type OAuthApi = {
|
||||
*/
|
||||
getAccessToken(
|
||||
scope?: OAuthScope,
|
||||
options?: AccessTokenOptions,
|
||||
options?: AuthRequestOptions,
|
||||
): Promise<string>;
|
||||
|
||||
/**
|
||||
@@ -97,29 +97,6 @@ export type OAuthApi = {
|
||||
logout(): Promise<void>;
|
||||
};
|
||||
|
||||
export type IdTokenOptions = {
|
||||
/**
|
||||
* If this is set to true, the user will not be prompted to log in,
|
||||
* and an empty id token will be returned if there is no existing session.
|
||||
*
|
||||
* This can be used to perform a check whether the user is logged in, or if you don't
|
||||
* want to force a user to be logged in, but provide functionality if they already are.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
optional?: boolean;
|
||||
|
||||
/**
|
||||
* If this is set to true, the request will bypass the regular oauth login modal
|
||||
* and open the login popup directly.
|
||||
*
|
||||
* The method must be called synchronously from a user action for this to work in all browsers.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
instantPopup?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* This API provides access to OpenID Connect credentials. It lets you request ID tokens,
|
||||
* which can be passed to backend services to prove the user's identity.
|
||||
@@ -136,7 +113,7 @@ export type OpenIdConnectApi = {
|
||||
* to log in. The returned promise will not resolve until the user has successfully logged in.
|
||||
* The returned promise can be rejected, but only if the user rejects the login request.
|
||||
*/
|
||||
getIdToken(options?: IdTokenOptions): Promise<string>;
|
||||
getIdToken(options?: AuthRequestOptions): Promise<string>;
|
||||
|
||||
/**
|
||||
* Log out the user's session. This will reload the page.
|
||||
@@ -144,38 +121,65 @@ export type OpenIdConnectApi = {
|
||||
logout(): Promise<void>;
|
||||
};
|
||||
|
||||
export type ProfileInfoOptions = {
|
||||
/**
|
||||
* If this is set to true, the user will not be prompted to log in,
|
||||
* and an empty profile will be returned if there is no existing session.
|
||||
*
|
||||
* This can be used to perform a check whether the user is logged in, or if you don't
|
||||
* want to force a user to be logged in, but provide functionality if they already are.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
optional?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* This API provides access to profile information of the user from an auth provider.
|
||||
*/
|
||||
export type ProfileInfoApi = {
|
||||
getProfile(options?: ProfileInfoOptions): Promise<ProfileInfo | undefined>;
|
||||
/**
|
||||
* Get profile information for the user as supplied by this auth provider.
|
||||
*
|
||||
* If the optional flag is not set, a session is guaranteed to be returned, while if
|
||||
* the optional flag is set, the session may be undefined. See @AuthRequestOptions for more details.
|
||||
*/
|
||||
getProfile(options?: AuthRequestOptions): Promise<ProfileInfo | undefined>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Profile information of the user from an auth provider.
|
||||
* This API provides access to the user's identity within Backstage.
|
||||
*
|
||||
* An auth provider that implements this interface can be used to sign-in to backstage. It is
|
||||
* not intended to be used directly from a plugin, but instead serves as a connection between
|
||||
* this authentication method and the app's @IdentityApi
|
||||
*/
|
||||
export type BackstageIdentityApi = {
|
||||
/**
|
||||
* Get the user's identity within Backstage. This should normally not be called directly,
|
||||
* use the @IdentityApi instead.
|
||||
*
|
||||
* If the optional flag is not set, a session is guaranteed to be returned, while if
|
||||
* the optional flag is set, the session may be undefined. See @AuthRequestOptions for more details.
|
||||
*/
|
||||
getBackstageIdentity(
|
||||
options?: AuthRequestOptions,
|
||||
): Promise<BackstageIdentity | undefined>;
|
||||
};
|
||||
|
||||
export type BackstageIdentity = {
|
||||
/**
|
||||
* The backstage user ID.
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* An ID token that can be used to authenticate the user within Backstage.
|
||||
*/
|
||||
idToken: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Profile information of the user.
|
||||
*/
|
||||
export type ProfileInfo = {
|
||||
/**
|
||||
* Email ID.
|
||||
*/
|
||||
email: string;
|
||||
|
||||
/**
|
||||
* Display name that can be presented to the user.
|
||||
*/
|
||||
name?: string;
|
||||
displayName?: string;
|
||||
|
||||
/**
|
||||
* URL to an avatar image of the user.
|
||||
*/
|
||||
@@ -207,7 +211,11 @@ export type SessionStateApi = {
|
||||
* email and expiration information. Do not rely on any other fields, as they might not be present.
|
||||
*/
|
||||
export const googleAuthApiRef = createApiRef<
|
||||
OAuthApi & OpenIdConnectApi & ProfileInfoApi & SessionStateApi
|
||||
OAuthApi &
|
||||
OpenIdConnectApi &
|
||||
ProfileInfoApi &
|
||||
BackstageIdentityApi &
|
||||
SessionStateApi
|
||||
>({
|
||||
id: 'core.auth.google',
|
||||
description: 'Provides authentication towards Google APIs and identities',
|
||||
@@ -219,7 +227,9 @@ export const googleAuthApiRef = createApiRef<
|
||||
* See https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/
|
||||
* for a full list of supported scopes.
|
||||
*/
|
||||
export const githubAuthApiRef = createApiRef<OAuthApi & SessionStateApi>({
|
||||
export const githubAuthApiRef = createApiRef<
|
||||
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionStateApi
|
||||
>({
|
||||
id: 'core.auth.github',
|
||||
description: 'Provides authentication towards Github APIs',
|
||||
});
|
||||
|
||||
@@ -20,7 +20,7 @@ describe('GithubAuth', () => {
|
||||
it('should get access token', async () => {
|
||||
const getSession = jest
|
||||
.fn()
|
||||
.mockResolvedValue({ accessToken: 'access-token' });
|
||||
.mockResolvedValue({ providerInfo: { accessToken: 'access-token' } });
|
||||
const githubAuth = new GithubAuth({ getSession } as any);
|
||||
|
||||
expect(await githubAuth.getAccessToken()).toBe('access-token');
|
||||
|
||||
@@ -19,9 +19,11 @@ import { DefaultAuthConnector } from '../../../../lib/AuthConnector';
|
||||
import { GithubSession } from './types';
|
||||
import {
|
||||
OAuthApi,
|
||||
AccessTokenOptions,
|
||||
SessionStateApi,
|
||||
SessionState,
|
||||
ProfileInfo,
|
||||
BackstageIdentity,
|
||||
AuthRequestOptions,
|
||||
} from '../../../definitions/auth';
|
||||
import { OAuthRequestApi, AuthProvider } from '../../../definitions';
|
||||
import { SessionManager } from '../../../../lib/AuthSessionManager/types';
|
||||
@@ -41,10 +43,13 @@ type CreateOptions = {
|
||||
};
|
||||
|
||||
export type GithubAuthResponse = {
|
||||
accessToken: string;
|
||||
idToken: string;
|
||||
scope: string;
|
||||
expiresInSeconds: number;
|
||||
providerInfo: {
|
||||
accessToken: string;
|
||||
scope: string;
|
||||
expiresInSeconds: number;
|
||||
};
|
||||
profile: ProfileInfo;
|
||||
backstageIdentity: BackstageIdentity;
|
||||
};
|
||||
|
||||
const DEFAULT_PROVIDER = {
|
||||
@@ -69,9 +74,14 @@ class GithubAuth implements OAuthApi, SessionStateApi {
|
||||
oauthRequestApi: oauthRequestApi,
|
||||
sessionTransform(res: GithubAuthResponse): GithubSession {
|
||||
return {
|
||||
accessToken: res.accessToken,
|
||||
scopes: GithubAuth.normalizeScope(res.scope),
|
||||
expiresAt: new Date(Date.now() + res.expiresInSeconds * 1000),
|
||||
...res,
|
||||
providerInfo: {
|
||||
accessToken: res.providerInfo.accessToken,
|
||||
scopes: GithubAuth.normalizeScope(res.providerInfo.scope),
|
||||
expiresAt: new Date(
|
||||
Date.now() + res.providerInfo.expiresInSeconds * 1000,
|
||||
),
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -79,7 +89,7 @@ class GithubAuth implements OAuthApi, SessionStateApi {
|
||||
const sessionManager = new StaticAuthSessionManager({
|
||||
connector,
|
||||
defaultScopes: new Set(['user']),
|
||||
sessionScopes: session => session.scopes,
|
||||
sessionScopes: (session: GithubSession) => session.providerInfo.scopes,
|
||||
});
|
||||
|
||||
return new GithubAuth(sessionManager);
|
||||
@@ -93,7 +103,7 @@ class GithubAuth implements OAuthApi, SessionStateApi {
|
||||
|
||||
constructor(private readonly sessionManager: SessionManager<GithubSession>) {}
|
||||
|
||||
async getAccessToken(scope?: string, options?: AccessTokenOptions) {
|
||||
async getAccessToken(scope?: string, options?: AuthRequestOptions) {
|
||||
const normalizedScopes = GithubAuth.normalizeScope(scope);
|
||||
const session = await this.sessionManager.getSession({
|
||||
...options,
|
||||
@@ -101,11 +111,25 @@ class GithubAuth implements OAuthApi, SessionStateApi {
|
||||
});
|
||||
this.sessionStateTracker.setIsSignedId(!!session);
|
||||
if (session) {
|
||||
return session.accessToken;
|
||||
return session.providerInfo.accessToken;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
async getBackstageIdentity(
|
||||
options: AuthRequestOptions = {},
|
||||
): Promise<BackstageIdentity | undefined> {
|
||||
const session = await this.sessionManager.getSession(options);
|
||||
this.sessionStateTracker.setIsSignedId(!!session);
|
||||
return session?.backstageIdentity;
|
||||
}
|
||||
|
||||
async getProfile(options: AuthRequestOptions = {}) {
|
||||
const session = await this.sessionManager.getSession(options);
|
||||
this.sessionStateTracker.setIsSignedId(!!session);
|
||||
return session?.profile;
|
||||
}
|
||||
|
||||
async logout() {
|
||||
await this.sessionManager.removeSession();
|
||||
this.sessionStateTracker.setIsSignedId(false);
|
||||
|
||||
@@ -14,8 +14,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ProfileInfo } from '../../..';
|
||||
import { BackstageIdentity } from '../../../definitions';
|
||||
|
||||
export type GithubSession = {
|
||||
accessToken: string;
|
||||
scopes: Set<string>;
|
||||
expiresAt: Date;
|
||||
providerInfo: {
|
||||
accessToken: string;
|
||||
scopes: Set<string>;
|
||||
expiresAt: Date;
|
||||
};
|
||||
profile: ProfileInfo;
|
||||
backstageIdentity: BackstageIdentity;
|
||||
};
|
||||
|
||||
@@ -23,9 +23,9 @@ const PREFIX = 'https://www.googleapis.com/auth/';
|
||||
|
||||
describe('GoogleAuth', () => {
|
||||
it('should get refreshed access token', async () => {
|
||||
const getSession = jest
|
||||
.fn()
|
||||
.mockResolvedValue({ accessToken: 'access-token', expiresAt: theFuture });
|
||||
const getSession = jest.fn().mockResolvedValue({
|
||||
providerInfo: { accessToken: 'access-token', expiresAt: theFuture },
|
||||
});
|
||||
const googleAuth = new GoogleAuth({ getSession } as any);
|
||||
|
||||
expect(await googleAuth.getAccessToken()).toBe('access-token');
|
||||
@@ -33,9 +33,9 @@ describe('GoogleAuth', () => {
|
||||
});
|
||||
|
||||
it('should get refreshed id token', async () => {
|
||||
const getSession = jest
|
||||
.fn()
|
||||
.mockResolvedValue({ idToken: 'id-token', expiresAt: theFuture });
|
||||
const getSession = jest.fn().mockResolvedValue({
|
||||
providerInfo: { idToken: 'id-token', expiresAt: theFuture },
|
||||
});
|
||||
const googleAuth = new GoogleAuth({ getSession } as any);
|
||||
|
||||
expect(await googleAuth.getIdToken()).toBe('id-token');
|
||||
@@ -43,9 +43,9 @@ describe('GoogleAuth', () => {
|
||||
});
|
||||
|
||||
it('should get optional id token', async () => {
|
||||
const getSession = jest
|
||||
.fn()
|
||||
.mockResolvedValue({ idToken: 'id-token', expiresAt: theFuture });
|
||||
const getSession = jest.fn().mockResolvedValue({
|
||||
providerInfo: { idToken: 'id-token', expiresAt: theFuture },
|
||||
});
|
||||
const googleAuth = new GoogleAuth({ getSession } as any);
|
||||
|
||||
expect(await googleAuth.getIdToken({ optional: true })).toBe('id-token');
|
||||
@@ -58,9 +58,11 @@ describe('GoogleAuth', () => {
|
||||
const getSession = jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce({
|
||||
accessToken: 'access-token',
|
||||
expiresAt: theFuture,
|
||||
scopes: new Set([`${PREFIX}not-enough`]),
|
||||
providerInfo: {
|
||||
accessToken: 'access-token',
|
||||
expiresAt: theFuture,
|
||||
scopes: new Set([`${PREFIX}not-enough`]),
|
||||
},
|
||||
})
|
||||
.mockRejectedValue(error);
|
||||
const googleAuth = new GoogleAuth({ getSession } as any);
|
||||
@@ -77,17 +79,21 @@ describe('GoogleAuth', () => {
|
||||
|
||||
it('should wait for all session refreshes', async () => {
|
||||
const initialSession = {
|
||||
idToken: 'token1',
|
||||
expiresAt: theFuture,
|
||||
scopes: new Set(),
|
||||
providerInfo: {
|
||||
idToken: 'token1',
|
||||
expiresAt: theFuture,
|
||||
scopes: new Set(),
|
||||
},
|
||||
};
|
||||
const getSession = jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce(initialSession)
|
||||
.mockResolvedValue({
|
||||
idToken: 'token2',
|
||||
expiresAt: theFuture,
|
||||
scopes: new Set(),
|
||||
providerInfo: {
|
||||
idToken: 'token2',
|
||||
expiresAt: theFuture,
|
||||
scopes: new Set(),
|
||||
},
|
||||
});
|
||||
const googleAuth = new GoogleAuth({ getSession } as any);
|
||||
|
||||
@@ -95,7 +101,7 @@ describe('GoogleAuth', () => {
|
||||
await expect(googleAuth.getIdToken()).resolves.toBe('token1');
|
||||
expect(getSession).toBeCalledTimes(1);
|
||||
|
||||
initialSession.expiresAt = thePast;
|
||||
initialSession.providerInfo.expiresAt = thePast;
|
||||
|
||||
const promise1 = googleAuth.getIdToken();
|
||||
const promise2 = googleAuth.getIdToken();
|
||||
|
||||
@@ -20,13 +20,13 @@ import { GoogleSession } from './types';
|
||||
import {
|
||||
OAuthApi,
|
||||
OpenIdConnectApi,
|
||||
IdTokenOptions,
|
||||
AccessTokenOptions,
|
||||
ProfileInfoApi,
|
||||
ProfileInfoOptions,
|
||||
ProfileInfo,
|
||||
SessionStateApi,
|
||||
SessionState,
|
||||
BackstageIdentityApi,
|
||||
AuthRequestOptions,
|
||||
BackstageIdentity,
|
||||
} from '../../../definitions/auth';
|
||||
import { OAuthRequestApi, AuthProvider } from '../../../definitions';
|
||||
import { SessionManager } from '../../../../lib/AuthSessionManager/types';
|
||||
@@ -46,11 +46,14 @@ type CreateOptions = {
|
||||
};
|
||||
|
||||
export type GoogleAuthResponse = {
|
||||
providerInfo: {
|
||||
accessToken: string;
|
||||
idToken: string;
|
||||
scope: string;
|
||||
expiresInSeconds: number;
|
||||
};
|
||||
profile: ProfileInfo;
|
||||
accessToken: string;
|
||||
idToken: string;
|
||||
scope: string;
|
||||
expiresInSeconds: number;
|
||||
backstageIdentity: BackstageIdentity;
|
||||
};
|
||||
|
||||
const DEFAULT_PROVIDER = {
|
||||
@@ -62,7 +65,12 @@ const DEFAULT_PROVIDER = {
|
||||
const SCOPE_PREFIX = 'https://www.googleapis.com/auth/';
|
||||
|
||||
class GoogleAuth
|
||||
implements OAuthApi, OpenIdConnectApi, ProfileInfoApi, SessionStateApi {
|
||||
implements
|
||||
OAuthApi,
|
||||
OpenIdConnectApi,
|
||||
ProfileInfoApi,
|
||||
BackstageIdentityApi,
|
||||
SessionStateApi {
|
||||
static create({
|
||||
apiOrigin,
|
||||
basePath,
|
||||
@@ -78,11 +86,15 @@ class GoogleAuth
|
||||
oauthRequestApi: oauthRequestApi,
|
||||
sessionTransform(res: GoogleAuthResponse): GoogleSession {
|
||||
return {
|
||||
profile: res.profile,
|
||||
idToken: res.idToken,
|
||||
accessToken: res.accessToken,
|
||||
scopes: GoogleAuth.normalizeScopes(res.scope),
|
||||
expiresAt: new Date(Date.now() + res.expiresInSeconds * 1000),
|
||||
...res,
|
||||
providerInfo: {
|
||||
idToken: res.providerInfo.idToken,
|
||||
accessToken: res.providerInfo.accessToken,
|
||||
scopes: GoogleAuth.normalizeScopes(res.providerInfo.scope),
|
||||
expiresAt: new Date(
|
||||
Date.now() + res.providerInfo.expiresInSeconds * 1000,
|
||||
),
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -94,9 +106,10 @@ class GoogleAuth
|
||||
`${SCOPE_PREFIX}userinfo.email`,
|
||||
`${SCOPE_PREFIX}userinfo.profile`,
|
||||
]),
|
||||
sessionScopes: session => session.scopes,
|
||||
sessionShouldRefresh: session => {
|
||||
const expiresInSec = (session.expiresAt.getTime() - Date.now()) / 1000;
|
||||
sessionScopes: (session: GoogleSession) => session.providerInfo.scopes,
|
||||
sessionShouldRefresh: (session: GoogleSession) => {
|
||||
const expiresInSec =
|
||||
(session.providerInfo.expiresAt.getTime() - Date.now()) / 1000;
|
||||
return expiresInSec < 60 * 5;
|
||||
},
|
||||
});
|
||||
@@ -114,7 +127,7 @@ class GoogleAuth
|
||||
|
||||
async getAccessToken(
|
||||
scope?: string | string[],
|
||||
options?: AccessTokenOptions,
|
||||
options?: AuthRequestOptions,
|
||||
) {
|
||||
const normalizedScopes = GoogleAuth.normalizeScopes(scope);
|
||||
const session = await this.sessionManager.getSession({
|
||||
@@ -123,16 +136,16 @@ class GoogleAuth
|
||||
});
|
||||
this.sessionStateTracker.setIsSignedId(!!session);
|
||||
if (session) {
|
||||
return session.accessToken;
|
||||
return session.providerInfo.accessToken;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
async getIdToken(options: IdTokenOptions = {}) {
|
||||
async getIdToken(options: AuthRequestOptions = {}) {
|
||||
const session = await this.sessionManager.getSession(options);
|
||||
this.sessionStateTracker.setIsSignedId(!!session);
|
||||
if (session) {
|
||||
return session.idToken;
|
||||
return session.providerInfo.idToken;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
@@ -142,13 +155,18 @@ class GoogleAuth
|
||||
this.sessionStateTracker.setIsSignedId(false);
|
||||
}
|
||||
|
||||
async getProfile(options: ProfileInfoOptions = {}) {
|
||||
async getBackstageIdentity(
|
||||
options: AuthRequestOptions = {},
|
||||
): Promise<BackstageIdentity | undefined> {
|
||||
const session = await this.sessionManager.getSession(options);
|
||||
this.sessionStateTracker.setIsSignedId(!!session);
|
||||
if (!session) {
|
||||
return undefined;
|
||||
}
|
||||
return session.profile;
|
||||
return session?.backstageIdentity;
|
||||
}
|
||||
|
||||
async getProfile(options: AuthRequestOptions = {}) {
|
||||
const session = await this.sessionManager.getSession(options);
|
||||
this.sessionStateTracker.setIsSignedId(!!session);
|
||||
return session?.profile;
|
||||
}
|
||||
|
||||
static normalizeScopes(scopes?: string | string[]): Set<string> {
|
||||
|
||||
@@ -14,12 +14,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ProfileInfo } from '../../../definitions';
|
||||
import { ProfileInfo, BackstageIdentity } from '../../../definitions';
|
||||
|
||||
export type GoogleSession = {
|
||||
providerInfo: {
|
||||
idToken: string;
|
||||
accessToken: string;
|
||||
scopes: Set<string>;
|
||||
expiresAt: Date;
|
||||
};
|
||||
profile: ProfileInfo;
|
||||
idToken: string;
|
||||
accessToken: string;
|
||||
scopes: Set<string>;
|
||||
expiresAt: Date;
|
||||
backstageIdentity: BackstageIdentity;
|
||||
};
|
||||
|
||||
@@ -17,7 +17,6 @@ import React, {
|
||||
ComponentType,
|
||||
FC,
|
||||
useMemo,
|
||||
useCallback,
|
||||
useState,
|
||||
ReactElement,
|
||||
} from 'react';
|
||||
@@ -258,24 +257,14 @@ export class PrivateAppImpl implements BackstageApp {
|
||||
component: ComponentType<SignInPageProps>;
|
||||
children: ReactElement;
|
||||
}> = ({ component: Component, children }) => {
|
||||
const [done, setDone] = useState(false);
|
||||
const [result, setResult] = useState<SignInResult>();
|
||||
|
||||
const onResult = useCallback(
|
||||
(result: SignInResult) => {
|
||||
if (done) {
|
||||
throw new Error('Identity result callback was called twice');
|
||||
}
|
||||
this.identityApi.setSignInResult(result);
|
||||
setDone(true);
|
||||
},
|
||||
[done],
|
||||
);
|
||||
|
||||
if (done) {
|
||||
if (result) {
|
||||
this.identityApi.setSignInResult(result);
|
||||
return children;
|
||||
}
|
||||
|
||||
return <Component onResult={onResult} />;
|
||||
return <Component onResult={setResult} />;
|
||||
};
|
||||
|
||||
const AppRouter: FC<{}> = ({ children }) => {
|
||||
@@ -293,6 +282,10 @@ export class PrivateAppImpl implements BackstageApp {
|
||||
if (!SignInPageComponent) {
|
||||
this.identityApi.setSignInResult({
|
||||
userId: 'guest',
|
||||
profile: {
|
||||
email: 'guest@example.com',
|
||||
displayName: 'Guest',
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { IdentityApi } from '../apis';
|
||||
import { IdentityApi, ProfileInfo } from '../apis';
|
||||
import { SignInResult } from './types';
|
||||
|
||||
/**
|
||||
@@ -24,6 +24,7 @@ import { SignInResult } from './types';
|
||||
export class AppIdentity implements IdentityApi {
|
||||
private hasIdentity = false;
|
||||
private userId?: string;
|
||||
private profile?: ProfileInfo;
|
||||
private idTokenFunc?: () => Promise<string>;
|
||||
private logoutFunc?: () => Promise<void>;
|
||||
|
||||
@@ -36,6 +37,15 @@ export class AppIdentity implements IdentityApi {
|
||||
return this.userId!;
|
||||
}
|
||||
|
||||
getProfile(): ProfileInfo {
|
||||
if (!this.hasIdentity) {
|
||||
throw new Error(
|
||||
'Tried to access IdentityApi profile before app was loaded',
|
||||
);
|
||||
}
|
||||
return this.profile!;
|
||||
}
|
||||
|
||||
async getIdToken(): Promise<string | undefined> {
|
||||
if (!this.hasIdentity) {
|
||||
throw new Error(
|
||||
@@ -55,6 +65,7 @@ export class AppIdentity implements IdentityApi {
|
||||
location.reload();
|
||||
}
|
||||
|
||||
// This is indirectly called by the sign-in page to continue into the app.
|
||||
setSignInResult(result: SignInResult) {
|
||||
if (this.hasIdentity) {
|
||||
return;
|
||||
@@ -62,8 +73,12 @@ export class AppIdentity implements IdentityApi {
|
||||
if (!result.userId) {
|
||||
throw new Error('Invalid sign-in result, userId not set');
|
||||
}
|
||||
if (!result.profile) {
|
||||
throw new Error('Invalid sign-in result, profile not set');
|
||||
}
|
||||
this.hasIdentity = true;
|
||||
this.userId = result.userId;
|
||||
this.profile = result.profile;
|
||||
this.idTokenFunc = result.getIdToken;
|
||||
this.logoutFunc = result.logout;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import { ComponentType } from 'react';
|
||||
import { IconComponent, SystemIconKey, SystemIcons } from '../icons';
|
||||
import { BackstagePlugin } from '../plugin';
|
||||
import { ApiHolder } from '../apis';
|
||||
import { AppTheme, ConfigApi } from '../apis/definitions';
|
||||
import { AppTheme, ConfigApi, ProfileInfo } from '../apis/definitions';
|
||||
import { AppConfig } from '@backstage/config';
|
||||
|
||||
export type BootErrorPageProps = {
|
||||
@@ -31,6 +31,9 @@ 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.
|
||||
*/
|
||||
|
||||
@@ -59,7 +59,7 @@ describe('showLoginPopup', () => {
|
||||
// None of these should be accepted
|
||||
listener({ source: popupMock } as MessageEvent);
|
||||
listener({ origin: 'my-origin' } as MessageEvent);
|
||||
listener({ data: { type: 'auth-result' } } as MessageEvent);
|
||||
listener({ data: { type: 'authorization_response' } } as MessageEvent);
|
||||
listener({
|
||||
source: popupMock,
|
||||
origin: 'my-origin',
|
||||
@@ -68,26 +68,26 @@ describe('showLoginPopup', () => {
|
||||
listener({
|
||||
source: popupMock,
|
||||
origin: 'my-origin',
|
||||
data: { type: 'not-auth-result', payload: {} },
|
||||
data: { type: 'not-auth-result', response: {} },
|
||||
} as MessageEvent);
|
||||
|
||||
await expect(Promise.race([payloadPromise, 'waiting'])).resolves.toBe(
|
||||
'waiting',
|
||||
);
|
||||
|
||||
const myPayload = {};
|
||||
const myResponse = {};
|
||||
|
||||
// This should be accepted as a valid sessions response
|
||||
listener({
|
||||
source: popupMock,
|
||||
origin: 'my-origin',
|
||||
data: {
|
||||
type: 'auth-result',
|
||||
payload: myPayload,
|
||||
type: 'authorization_response',
|
||||
response: myResponse,
|
||||
},
|
||||
} as MessageEvent);
|
||||
|
||||
await expect(payloadPromise).resolves.toBe(myPayload);
|
||||
await expect(payloadPromise).resolves.toBe(myResponse);
|
||||
|
||||
expect(openSpy).toBeCalledTimes(1);
|
||||
expect(addEventListenerSpy).toBeCalledTimes(1);
|
||||
@@ -118,7 +118,7 @@ describe('showLoginPopup', () => {
|
||||
source: popupMock,
|
||||
origin: 'my-origin',
|
||||
data: {
|
||||
type: 'auth-result',
|
||||
type: 'authorization_response',
|
||||
error: {
|
||||
message: 'NOPE',
|
||||
name: 'NopeError',
|
||||
|
||||
@@ -46,11 +46,11 @@ export type LoginPopupOptions = {
|
||||
|
||||
type AuthResult =
|
||||
| {
|
||||
type: 'auth-result';
|
||||
payload: any;
|
||||
type: 'authorization_response';
|
||||
response: unknown;
|
||||
}
|
||||
| {
|
||||
type: 'auth-result';
|
||||
type: 'authorization_response';
|
||||
error: {
|
||||
name: string;
|
||||
message: string;
|
||||
@@ -58,12 +58,13 @@ type AuthResult =
|
||||
};
|
||||
|
||||
/**
|
||||
* Show a popup pointing to a URL that starts an auth flow.
|
||||
* Show a popup pointing to a URL that starts an auth flow. Implementing the receiving
|
||||
* end of the postMessage mechanism outlined in https://tools.ietf.org/html/draft-sakimura-oauth-wmrm-00
|
||||
*
|
||||
* The redirect handler of the flow should use postMessage to communicate back
|
||||
* to the app window. The message posted to the app must match the AuthResult type.
|
||||
*
|
||||
* The returned promise resolves to the contents of the message that was posted from the auth popup.
|
||||
* The returned promise resolves to the response of the message that was posted from the auth popup.
|
||||
*/
|
||||
export function showLoginPopup(options: LoginPopupOptions): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -91,7 +92,7 @@ export function showLoginPopup(options: LoginPopupOptions): Promise<any> {
|
||||
return;
|
||||
}
|
||||
const { data } = event;
|
||||
if (data.type !== 'auth-result') {
|
||||
if (data.type !== 'authorization_response') {
|
||||
return;
|
||||
}
|
||||
const authResult = data as AuthResult;
|
||||
@@ -103,7 +104,7 @@ export function showLoginPopup(options: LoginPopupOptions): Promise<any> {
|
||||
// error.extra = authResult.error.extra;
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(authResult.payload);
|
||||
resolve(authResult.response);
|
||||
}
|
||||
done();
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/core",
|
||||
"description": "Core API used by Backstage plugins and apps",
|
||||
"version": "0.1.1-alpha.10",
|
||||
"version": "0.1.1-alpha.12",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -29,9 +29,9 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/config": "^0.1.1-alpha.10",
|
||||
"@backstage/core-api": "^0.1.1-alpha.10",
|
||||
"@backstage/theme": "^0.1.1-alpha.10",
|
||||
"@backstage/config": "^0.1.1-alpha.12",
|
||||
"@backstage/core-api": "^0.1.1-alpha.12",
|
||||
"@backstage/theme": "^0.1.1-alpha.12",
|
||||
"@material-ui/core": "^4.9.1",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
@@ -54,8 +54,8 @@
|
||||
"react-use": "^14.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.10",
|
||||
"@backstage/test-utils": "^0.1.1-alpha.10",
|
||||
"@backstage/cli": "^0.1.1-alpha.12",
|
||||
"@backstage/test-utils": "^0.1.1-alpha.12",
|
||||
"@testing-library/jest-dom": "^5.7.0",
|
||||
"@testing-library/react": "^9.3.2",
|
||||
"@testing-library/user-event": "^10.2.4",
|
||||
@@ -68,6 +68,6 @@
|
||||
"jest-fetch-mock": "^3.0.3"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*.{js,d.ts}"
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -41,22 +41,29 @@ export const OAuthProviderSettings: FC<OAuthProviderSidebarProps> = ({
|
||||
const [signedIn, setSignedIn] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
let didCancel = false;
|
||||
|
||||
const checkSession = async () => {
|
||||
const session = await api.getAccessToken('', { optional: true });
|
||||
setSignedIn(!!session);
|
||||
if (!didCancel) {
|
||||
setSignedIn(!!session);
|
||||
}
|
||||
};
|
||||
let subscription: Subscription;
|
||||
const observeSession = () => {
|
||||
subscription = api
|
||||
.sessionState$()
|
||||
.subscribe((sessionState: SessionState) => {
|
||||
setSignedIn(sessionState === SessionState.SignedIn);
|
||||
if (!didCancel) {
|
||||
setSignedIn(sessionState === SessionState.SignedIn);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
checkSession();
|
||||
observeSession();
|
||||
return () => {
|
||||
didCancel = true;
|
||||
subscription.unsubscribe();
|
||||
};
|
||||
}, [api]);
|
||||
|
||||
@@ -41,9 +41,13 @@ export const OIDCProviderSettings: FC<OIDCProviderSidebarProps> = ({
|
||||
const [signedIn, setSignedIn] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
let didCancel = false;
|
||||
|
||||
const checkSession = async () => {
|
||||
const session = await api.getIdToken({ optional: true });
|
||||
setSignedIn(!!session);
|
||||
if (!didCancel) {
|
||||
setSignedIn(!!session);
|
||||
}
|
||||
};
|
||||
|
||||
let subscription: Subscription;
|
||||
@@ -51,13 +55,16 @@ export const OIDCProviderSettings: FC<OIDCProviderSidebarProps> = ({
|
||||
subscription = api
|
||||
.sessionState$()
|
||||
.subscribe((sessionState: SessionState) => {
|
||||
setSignedIn(sessionState === SessionState.SignedIn);
|
||||
if (!didCancel) {
|
||||
setSignedIn(sessionState === SessionState.SignedIn);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
checkSession();
|
||||
observeSession();
|
||||
return () => {
|
||||
didCancel = true;
|
||||
subscription.unsubscribe();
|
||||
};
|
||||
}, [api]);
|
||||
|
||||
@@ -14,19 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { FC, useState, useRef, useEffect } from 'react';
|
||||
import React, { FC, useRef } from 'react';
|
||||
import { makeStyles, Avatar, Divider } from '@material-ui/core';
|
||||
import {
|
||||
ProfileInfo,
|
||||
useApi,
|
||||
googleAuthApiRef,
|
||||
Subscription,
|
||||
SessionState,
|
||||
} from '@backstage/core-api';
|
||||
import { useApi, identityApiRef } from '@backstage/core-api';
|
||||
import { SidebarItem } from '../Items';
|
||||
import ExpandLess from '@material-ui/icons/ExpandLess';
|
||||
import ExpandMore from '@material-ui/icons/ExpandMore';
|
||||
import AccountCircleIcon from '@material-ui/icons/AccountCircle';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
avatar: {
|
||||
@@ -39,71 +32,26 @@ export const UserProfile: FC<{ open: boolean; setOpen: Function }> = ({
|
||||
open,
|
||||
setOpen,
|
||||
}) => {
|
||||
const [profile, setProfile] = useState<ProfileInfo>();
|
||||
const ref = useRef<Element>(); // for scrolling down when collapse item opens
|
||||
const googleAuth = useApi(googleAuthApiRef);
|
||||
const classes = useStyles();
|
||||
const profile = useApi(identityApiRef).getProfile();
|
||||
|
||||
const handleClick = () => {
|
||||
setOpen(!open);
|
||||
setTimeout(() => ref.current?.scrollIntoView({ behavior: 'smooth' }), 300);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const fetchProfile = async () => {
|
||||
await googleAuth
|
||||
.getProfile({ optional: true })
|
||||
.then((userProfile?: ProfileInfo) => {
|
||||
setProfile(userProfile);
|
||||
});
|
||||
};
|
||||
|
||||
let subscription: Subscription;
|
||||
const observeSession = () => {
|
||||
subscription = googleAuth
|
||||
.sessionState$()
|
||||
.subscribe(async (sessionState: SessionState) => {
|
||||
if (sessionState === SessionState.SignedIn) {
|
||||
await fetchProfile();
|
||||
} else {
|
||||
setProfile(undefined);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
fetchProfile();
|
||||
observeSession();
|
||||
return () => {
|
||||
subscription.unsubscribe();
|
||||
};
|
||||
}, [googleAuth]);
|
||||
|
||||
// Handle main auth info that is shown on the collapsible SidebarItem
|
||||
let avatar;
|
||||
let displayName = 'Guest';
|
||||
if (profile) {
|
||||
const email = profile.email;
|
||||
const name = profile.name;
|
||||
const imageUrl = profile.picture;
|
||||
const emailTrimmed = email.split('@')[0];
|
||||
const displayEmail =
|
||||
emailTrimmed.charAt(0).toUpperCase() + emailTrimmed.slice(1);
|
||||
displayName = name ?? displayEmail;
|
||||
avatar = imageUrl
|
||||
? () => (
|
||||
<Avatar alt={displayName} src={imageUrl} className={classes.avatar} />
|
||||
)
|
||||
: () => <Avatar alt={displayName} className={classes.avatar} />;
|
||||
}
|
||||
const displayName = profile.displayName ?? profile.email;
|
||||
const SignInAvatar = () => (
|
||||
<Avatar src={profile.picture} className={classes.avatar}>
|
||||
{displayName[0]}
|
||||
</Avatar>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Divider innerRef={ref} />
|
||||
<SidebarItem
|
||||
text={displayName}
|
||||
onClick={handleClick}
|
||||
icon={avatar || AccountCircleIcon}
|
||||
>
|
||||
<SidebarItem text={displayName} onClick={handleClick} icon={SignInAvatar}>
|
||||
{open ? <ExpandMore /> : <ExpandLess />}
|
||||
</SidebarItem>
|
||||
</>
|
||||
|
||||
@@ -56,6 +56,9 @@ const Component: ProviderComponent = ({ onResult }) => {
|
||||
const handleResult = ({ userId, idToken }: Data) => {
|
||||
onResult({
|
||||
userId,
|
||||
profile: {
|
||||
email: `${userId}@example.com`,
|
||||
},
|
||||
getIdToken: idToken ? async () => idToken : undefined,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -18,16 +18,7 @@ import React from 'react';
|
||||
import { Grid, Typography, Button } from '@material-ui/core';
|
||||
import { InfoCard } from '../InfoCard/InfoCard';
|
||||
import { ProviderComponent, ProviderLoader, SignInProvider } from './types';
|
||||
import {
|
||||
useApi,
|
||||
googleAuthApiRef,
|
||||
errorApiRef,
|
||||
ProfileInfo,
|
||||
} from '@backstage/core-api';
|
||||
|
||||
function parseUserId(profile: ProfileInfo) {
|
||||
return profile!.email.replace(/@.*/, '');
|
||||
}
|
||||
import { useApi, googleAuthApiRef, errorApiRef } from '@backstage/core-api';
|
||||
|
||||
const Component: ProviderComponent = ({ onResult }) => {
|
||||
const googleAuthApi = useApi(googleAuthApiRef);
|
||||
@@ -35,12 +26,17 @@ const Component: ProviderComponent = ({ onResult }) => {
|
||||
|
||||
const handleLogin = async () => {
|
||||
try {
|
||||
await googleAuthApi.getIdToken({ instantPopup: true });
|
||||
const identity = await googleAuthApi.getBackstageIdentity({
|
||||
instantPopup: true,
|
||||
});
|
||||
|
||||
const profile = await googleAuthApi.getProfile();
|
||||
|
||||
onResult({
|
||||
userId: parseUserId(profile!),
|
||||
getIdToken: () => googleAuthApi.getIdToken(),
|
||||
userId: identity!.id,
|
||||
profile: profile!,
|
||||
getIdToken: () =>
|
||||
googleAuthApi.getBackstageIdentity().then(i => i!.idToken),
|
||||
logout: async () => {
|
||||
await googleAuthApi.logout();
|
||||
},
|
||||
@@ -69,11 +65,21 @@ const Component: ProviderComponent = ({ onResult }) => {
|
||||
const loader: ProviderLoader = async apis => {
|
||||
const googleAuthApi = apis.get(googleAuthApiRef)!;
|
||||
|
||||
const profile = await googleAuthApi.getProfile({ optional: true });
|
||||
const identity = await googleAuthApi.getBackstageIdentity({
|
||||
optional: true,
|
||||
});
|
||||
|
||||
if (!identity) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const profile = await googleAuthApi.getProfile();
|
||||
|
||||
return {
|
||||
userId: parseUserId(profile!),
|
||||
getIdToken: () => googleAuthApi.getIdToken(),
|
||||
userId: identity.id,
|
||||
profile: profile!,
|
||||
getIdToken: () =>
|
||||
googleAuthApi.getBackstageIdentity().then(i => i!.idToken),
|
||||
logout: async () => {
|
||||
await googleAuthApi.logout();
|
||||
},
|
||||
|
||||
@@ -19,6 +19,14 @@ import { Grid, Typography, Button } from '@material-ui/core';
|
||||
import { InfoCard } from '../InfoCard/InfoCard';
|
||||
import { ProviderComponent, ProviderLoader, SignInProvider } from './types';
|
||||
|
||||
const result = {
|
||||
userId: 'guest',
|
||||
profile: {
|
||||
email: 'guest@example.com',
|
||||
displayName: 'Guest',
|
||||
},
|
||||
};
|
||||
|
||||
const Component: ProviderComponent = ({ onResult }) => (
|
||||
<Grid item>
|
||||
<InfoCard
|
||||
@@ -27,7 +35,7 @@ const Component: ProviderComponent = ({ onResult }) => (
|
||||
<Button
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
onClick={() => onResult({ userId: 'guest' })}
|
||||
onClick={() => onResult(result)}
|
||||
>
|
||||
Enter
|
||||
</Button>
|
||||
@@ -45,7 +53,7 @@ const Component: ProviderComponent = ({ onResult }) => (
|
||||
);
|
||||
|
||||
const loader: ProviderLoader = async () => {
|
||||
return { userId: 'guest' };
|
||||
return result;
|
||||
};
|
||||
|
||||
export const guestProvider: SignInProvider = { Component, loader };
|
||||
|
||||
@@ -93,8 +93,9 @@ export const useSignInProviders = (
|
||||
}
|
||||
if (result) {
|
||||
handleWrappedResult(result);
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(error => {
|
||||
if (didCancel) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/dev-utils",
|
||||
"description": "Utilities for developing Backstage plugins.",
|
||||
"version": "0.1.1-alpha.10",
|
||||
"version": "0.1.1-alpha.12",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -29,10 +29,10 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.10",
|
||||
"@backstage/core": "^0.1.1-alpha.10",
|
||||
"@backstage/test-utils": "^0.1.1-alpha.10",
|
||||
"@backstage/theme": "^0.1.1-alpha.10",
|
||||
"@backstage/cli": "^0.1.1-alpha.12",
|
||||
"@backstage/core": "^0.1.1-alpha.12",
|
||||
"@backstage/test-utils": "^0.1.1-alpha.12",
|
||||
"@backstage/theme": "^0.1.1-alpha.12",
|
||||
"@material-ui/core": "^4.9.1",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@testing-library/jest-dom": "^5.7.0",
|
||||
@@ -50,6 +50,6 @@
|
||||
"@types/node": "^12.0.0"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*.{js,d.ts}"
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -24,6 +24,10 @@ import {
|
||||
AlertApiForwarder,
|
||||
oauthRequestApiRef,
|
||||
OAuthRequestManager,
|
||||
GoogleAuth,
|
||||
googleAuthApiRef,
|
||||
GithubAuth,
|
||||
githubAuthApiRef,
|
||||
} from '@backstage/core';
|
||||
|
||||
// TODO(rugvip): We should likely figure out how to reuse all of these between apps
|
||||
@@ -49,3 +53,25 @@ export const oauthRequestApiFactory = createApiFactory({
|
||||
deps: {},
|
||||
factory: () => new OAuthRequestManager(),
|
||||
});
|
||||
|
||||
export const googleAuthApiFactory = createApiFactory({
|
||||
implements: googleAuthApiRef,
|
||||
deps: { oauthRequestApi: oauthRequestApiRef },
|
||||
factory: ({ oauthRequestApi }) =>
|
||||
GoogleAuth.create({
|
||||
apiOrigin: 'http://localhost:7000',
|
||||
basePath: '/auth/',
|
||||
oauthRequestApi,
|
||||
}),
|
||||
});
|
||||
|
||||
export const githubAuthApiFactory = createApiFactory({
|
||||
implements: githubAuthApiRef,
|
||||
deps: { oauthRequestApi: oauthRequestApiRef },
|
||||
factory: ({ oauthRequestApi }) =>
|
||||
GithubAuth.create({
|
||||
apiOrigin: 'http://localhost:7000',
|
||||
basePath: '/auth/',
|
||||
oauthRequestApi,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -22,6 +22,7 @@ builder.add(errorApiRef, new ErrorAlerter(alertApi, new ErrorApiForwarder()));
|
||||
|
||||
builder.add(identityApiRef, {
|
||||
getUserId: () => 'guest',
|
||||
getProfile: () => ({ email: 'guest@example.com' }),
|
||||
getIdToken: () => undefined,
|
||||
logout: async () => {},
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "storybook",
|
||||
"version": "0.1.1-alpha.10",
|
||||
"version": "0.1.1-alpha.12",
|
||||
"description": "Storybook build for core package",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -14,7 +14,7 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/theme": "^0.1.1-alpha.10"
|
||||
"@backstage/theme": "^0.1.1-alpha.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@storybook/addon-actions": "^5.3.17",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/techdocs-cli",
|
||||
"description": "CLI for running TechDocs locally.",
|
||||
"version": "0.1.1-alpha.10",
|
||||
"version": "0.1.1-alpha.12",
|
||||
"private": true,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
@@ -28,7 +28,7 @@
|
||||
"techdocs": "bin/techdocs-cli"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.10"
|
||||
"@backstage/cli": "^0.1.1-alpha.12"
|
||||
},
|
||||
"files": [
|
||||
"bin",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/test-utils-core",
|
||||
"description": "Utilities to test Backstage core",
|
||||
"version": "0.1.1-alpha.10",
|
||||
"version": "0.1.1-alpha.12",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -40,6 +40,6 @@
|
||||
"@types/node": "^12.0.0"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*.{js,d.ts}"
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/test-utils",
|
||||
"description": "Utilities to test Backstage plugins and apps.",
|
||||
"version": "0.1.1-alpha.10",
|
||||
"version": "0.1.1-alpha.12",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -29,10 +29,10 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.10",
|
||||
"@backstage/core-api": "^0.1.1-alpha.10",
|
||||
"@backstage/test-utils-core": "^0.1.1-alpha.10",
|
||||
"@backstage/theme": "^0.1.1-alpha.10",
|
||||
"@backstage/cli": "^0.1.1-alpha.12",
|
||||
"@backstage/core-api": "^0.1.1-alpha.12",
|
||||
"@backstage/test-utils-core": "^0.1.1-alpha.12",
|
||||
"@backstage/theme": "^0.1.1-alpha.12",
|
||||
"@material-ui/core": "^4.9.1",
|
||||
"@testing-library/jest-dom": "^5.7.0",
|
||||
"@testing-library/react": "^9.3.2",
|
||||
@@ -48,6 +48,6 @@
|
||||
"@types/node": "^12.0.0"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*.{js,d.ts}"
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/theme",
|
||||
"description": "material-ui theme for use with Backstage.",
|
||||
"version": "0.1.1-alpha.10",
|
||||
"version": "0.1.1-alpha.12",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -31,9 +31,9 @@
|
||||
"@material-ui/core": "^4.9.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.10"
|
||||
"@backstage/cli": "^0.1.1-alpha.12"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*.{js,d.ts}"
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user