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:
+1
-1
@@ -2,5 +2,5 @@
|
||||
"packages": ["packages/*", "plugins/*"],
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"version": "0.1.1-alpha.10"
|
||||
"version": "0.1.1-alpha.12"
|
||||
}
|
||||
|
||||
+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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-auth-backend",
|
||||
"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,9 +20,9 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.1.1-alpha.10",
|
||||
"@backstage/config": "^0.1.1-alpha.10",
|
||||
"@backstage/config-loader": "^0.1.1-alpha.10",
|
||||
"@backstage/backend-common": "^0.1.1-alpha.12",
|
||||
"@backstage/config": "^0.1.1-alpha.12",
|
||||
"@backstage/config-loader": "^0.1.1-alpha.12",
|
||||
"@types/express": "^4.17.6",
|
||||
"body-parser": "^1.19.0",
|
||||
"compression": "^1.7.4",
|
||||
@@ -46,7 +46,7 @@
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.10",
|
||||
"@backstage/cli": "^0.1.1-alpha.12",
|
||||
"@types/body-parser": "^1.19.0",
|
||||
"@types/cookie-parser": "^1.4.2",
|
||||
"@types/jwt-decode": "2.2.1",
|
||||
@@ -57,6 +57,6 @@
|
||||
"jest-fetch-mock": "^3.0.3"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*.{js,d.ts}"
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -23,7 +23,23 @@ import {
|
||||
verifyNonce,
|
||||
OAuthProvider,
|
||||
} from './OAuthProvider';
|
||||
import { AuthResponse, OAuthProviderHandlers } from '../providers/types';
|
||||
import {
|
||||
WebMessageResponse,
|
||||
OAuthProviderHandlers,
|
||||
OAuthResponse,
|
||||
} from '../providers/types';
|
||||
|
||||
const mockResponseData: OAuthResponse = {
|
||||
providerInfo: {
|
||||
accessToken: 'ACCESS_TOKEN',
|
||||
idToken: 'ID_TOKEN',
|
||||
expiresInSeconds: 10,
|
||||
scope: 'email',
|
||||
},
|
||||
profile: {
|
||||
email: 'foo@bar.com',
|
||||
},
|
||||
};
|
||||
|
||||
describe('OAuthProvider Utils', () => {
|
||||
describe('verifyNonce', () => {
|
||||
@@ -38,6 +54,7 @@ describe('OAuthProvider Utils', () => {
|
||||
verifyNonce(mockRequest, 'providera');
|
||||
}).toThrowError('Missing nonce');
|
||||
});
|
||||
|
||||
it('should throw error if state nonce missing', () => {
|
||||
const mockRequest = ({
|
||||
cookies: {
|
||||
@@ -49,6 +66,7 @@ describe('OAuthProvider Utils', () => {
|
||||
verifyNonce(mockRequest, 'providera');
|
||||
}).toThrowError('Missing nonce');
|
||||
});
|
||||
|
||||
it('should throw error if nonce mismatch', () => {
|
||||
const mockRequest = ({
|
||||
cookies: {
|
||||
@@ -62,6 +80,7 @@ describe('OAuthProvider Utils', () => {
|
||||
verifyNonce(mockRequest, 'providera');
|
||||
}).toThrowError('Invalid nonce');
|
||||
});
|
||||
|
||||
it('should not throw any error if nonce matches', () => {
|
||||
const mockRequest = ({
|
||||
cookies: {
|
||||
@@ -85,13 +104,22 @@ describe('OAuthProvider Utils', () => {
|
||||
setHeader: jest.fn().mockReturnThis(),
|
||||
} as unknown) as express.Response;
|
||||
|
||||
const data: AuthResponse = {
|
||||
type: 'auth-result',
|
||||
payload: {
|
||||
accessToken: 'ACCESS_TOKEN',
|
||||
idToken: 'ID_TOKEN',
|
||||
expiresInSeconds: 10,
|
||||
scope: 'email',
|
||||
const data: WebMessageResponse = {
|
||||
type: 'authorization_response',
|
||||
response: {
|
||||
providerInfo: {
|
||||
accessToken: 'ACCESS_TOKEN',
|
||||
idToken: 'ID_TOKEN',
|
||||
expiresInSeconds: 10,
|
||||
scope: 'email',
|
||||
},
|
||||
profile: {
|
||||
email: 'foo@bar.com',
|
||||
},
|
||||
backstageIdentity: {
|
||||
id: 'a',
|
||||
idToken: 'a.b.c',
|
||||
},
|
||||
},
|
||||
};
|
||||
const jsonData = JSON.stringify(data);
|
||||
@@ -111,8 +139,8 @@ describe('OAuthProvider Utils', () => {
|
||||
setHeader: jest.fn().mockReturnThis(),
|
||||
} as unknown) as express.Response;
|
||||
|
||||
const data: AuthResponse = {
|
||||
type: 'auth-result',
|
||||
const data: WebMessageResponse = {
|
||||
type: 'authorization_response',
|
||||
error: new Error('Unknown error occured'),
|
||||
};
|
||||
const jsonData = JSON.stringify(data);
|
||||
@@ -161,16 +189,12 @@ describe('OAuthProvider', () => {
|
||||
}
|
||||
async handler() {
|
||||
return {
|
||||
user: {},
|
||||
info: {
|
||||
refreshToken: 'token',
|
||||
},
|
||||
response: mockResponseData,
|
||||
refreshToken: 'token',
|
||||
};
|
||||
}
|
||||
async refresh() {
|
||||
return {
|
||||
accessToken: 'token',
|
||||
};
|
||||
return mockResponseData;
|
||||
}
|
||||
}
|
||||
const providerInstance = new MyAuthProvider();
|
||||
@@ -323,11 +347,13 @@ describe('OAuthProvider', () => {
|
||||
|
||||
await oauthProvider.refresh(mockRequest, mockResponse);
|
||||
expect(mockResponse.send).toHaveBeenCalledTimes(1);
|
||||
expect(mockResponse.send).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
accessToken: 'token',
|
||||
}),
|
||||
);
|
||||
expect(mockResponse.send).toHaveBeenCalledWith({
|
||||
...mockResponseData,
|
||||
backstageIdentity: {
|
||||
id: mockResponseData.profile.email,
|
||||
idToken: 'my-id-token',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('handles refresh without capabilities', async () => {
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
AuthResponse,
|
||||
AuthProviderRouteHandlers,
|
||||
OAuthProviderHandlers,
|
||||
WebMessageResponse,
|
||||
} from '../providers/types';
|
||||
import { InputError } from '@backstage/backend-common';
|
||||
import { TokenIssuer } from '../identity';
|
||||
@@ -53,9 +54,9 @@ export const verifyNonce = (req: express.Request, providerId: string) => {
|
||||
export const postMessageResponse = (
|
||||
res: express.Response,
|
||||
appOrigin: string,
|
||||
data: AuthResponse,
|
||||
response: WebMessageResponse,
|
||||
) => {
|
||||
const jsonData = JSON.stringify(data);
|
||||
const jsonData = JSON.stringify(response);
|
||||
const base64Data = Buffer.from(jsonData, 'utf8').toString('base64');
|
||||
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
@@ -96,7 +97,7 @@ export class OAuthProvider implements AuthProviderRouteHandlers {
|
||||
this.basePath = url.pathname;
|
||||
}
|
||||
|
||||
async start(req: express.Request, res: express.Response): Promise<any> {
|
||||
async start(req: express.Request, res: express.Response): Promise<void> {
|
||||
// retrieve scopes from request
|
||||
const scope = req.query.scope?.toString() ?? '';
|
||||
|
||||
@@ -108,14 +109,15 @@ export class OAuthProvider implements AuthProviderRouteHandlers {
|
||||
// set a nonce cookie before redirecting to oauth provider
|
||||
this.setNonceCookie(res, nonce);
|
||||
|
||||
const options = {
|
||||
const queryParameters = {
|
||||
scope,
|
||||
accessType: 'offline',
|
||||
prompt: 'consent',
|
||||
state: nonce,
|
||||
};
|
||||
|
||||
const { url, status } = await this.providerHandlers.start(req, options);
|
||||
const { url, status } = await this.providerHandlers.start(
|
||||
req,
|
||||
queryParameters,
|
||||
);
|
||||
|
||||
res.statusCode = status || 302;
|
||||
res.setHeader('Location', url);
|
||||
@@ -126,16 +128,17 @@ export class OAuthProvider implements AuthProviderRouteHandlers {
|
||||
async frameHandler(
|
||||
req: express.Request,
|
||||
res: express.Response,
|
||||
): Promise<any> {
|
||||
): Promise<void> {
|
||||
try {
|
||||
// verify nonce cookie and state cookie on callback
|
||||
verifyNonce(req, this.options.providerId);
|
||||
|
||||
const { user, info } = await this.providerHandlers.handler(req);
|
||||
const { response, refreshToken } = await this.providerHandlers.handler(
|
||||
req,
|
||||
);
|
||||
|
||||
if (!this.options.disableRefresh) {
|
||||
// throw error if missing refresh token
|
||||
const { refreshToken } = info;
|
||||
if (!refreshToken) {
|
||||
throw new Error('Missing refresh token');
|
||||
}
|
||||
@@ -144,19 +147,24 @@ export class OAuthProvider implements AuthProviderRouteHandlers {
|
||||
this.setRefreshTokenCookie(res, refreshToken);
|
||||
}
|
||||
|
||||
user.userIdToken = await this.options.tokenIssuer.issueToken({
|
||||
claims: { sub: user.profile.email },
|
||||
const id = response.profile.email;
|
||||
const idToken = await this.options.tokenIssuer.issueToken({
|
||||
claims: { sub: id },
|
||||
});
|
||||
const fullResponse: AuthResponse<unknown> = {
|
||||
...response,
|
||||
backstageIdentity: { id, idToken },
|
||||
};
|
||||
|
||||
// post message back to popup if successful
|
||||
return postMessageResponse(res, this.options.appOrigin, {
|
||||
type: 'auth-result',
|
||||
payload: user,
|
||||
type: 'authorization_response',
|
||||
response: fullResponse,
|
||||
});
|
||||
} catch (error) {
|
||||
// post error message back to popup if failure
|
||||
return postMessageResponse(res, this.options.appOrigin, {
|
||||
type: 'auth-result',
|
||||
type: 'authorization_response',
|
||||
error: {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
@@ -165,27 +173,30 @@ export class OAuthProvider implements AuthProviderRouteHandlers {
|
||||
}
|
||||
}
|
||||
|
||||
async logout(req: express.Request, res: express.Response): Promise<any> {
|
||||
async logout(req: express.Request, res: express.Response): Promise<void> {
|
||||
if (!ensuresXRequestedWith(req)) {
|
||||
return res.status(401).send('Invalid X-Requested-With header');
|
||||
res.status(401).send('Invalid X-Requested-With header');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.options.disableRefresh) {
|
||||
// remove refresh token cookie before logout
|
||||
this.removeRefreshTokenCookie(res);
|
||||
}
|
||||
return res.send('logout!');
|
||||
res.send('logout!');
|
||||
}
|
||||
|
||||
async refresh(req: express.Request, res: express.Response): Promise<any> {
|
||||
async refresh(req: express.Request, res: express.Response): Promise<void> {
|
||||
if (!ensuresXRequestedWith(req)) {
|
||||
return res.status(401).send('Invalid X-Requested-With header');
|
||||
res.status(401).send('Invalid X-Requested-With header');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.providerHandlers.refresh || this.options.disableRefresh) {
|
||||
return res.send(
|
||||
res.send(
|
||||
`Refresh token not supported for provider: ${this.options.providerId}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -200,18 +211,20 @@ export class OAuthProvider implements AuthProviderRouteHandlers {
|
||||
const scope = req.query.scope?.toString() ?? '';
|
||||
|
||||
// get new access_token
|
||||
const refreshInfo = await this.providerHandlers.refresh(
|
||||
refreshToken,
|
||||
scope,
|
||||
);
|
||||
const response = await this.providerHandlers.refresh(refreshToken, scope);
|
||||
|
||||
refreshInfo.userIdToken = await this.options.tokenIssuer.issueToken({
|
||||
claims: { sub: refreshInfo.profile?.email },
|
||||
const id = response.profile.email;
|
||||
const idToken = await this.options.tokenIssuer.issueToken({
|
||||
claims: { sub: id },
|
||||
});
|
||||
const fullResponse: AuthResponse<unknown> = {
|
||||
...response,
|
||||
backstageIdentity: { id, idToken },
|
||||
};
|
||||
|
||||
return res.send(refreshInfo);
|
||||
res.send(fullResponse);
|
||||
} catch (error) {
|
||||
return res.status(401).send(`${error.message}`);
|
||||
res.status(401).send(`${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -82,8 +82,8 @@ describe('PassportStrategyHelper', () => {
|
||||
expect(spyAuthenticate).toBeCalledTimes(1);
|
||||
await expect(frameHandlerStrategyPromise).resolves.toStrictEqual(
|
||||
expect.objectContaining({
|
||||
user: { accessToken: 'ACCESS_TOKEN' },
|
||||
info: { refreshToken: 'REFRESH_TOKEN' },
|
||||
response: { accessToken: 'ACCESS_TOKEN' },
|
||||
privateInfo: { refreshToken: 'REFRESH_TOKEN' },
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -26,42 +26,52 @@ import {
|
||||
|
||||
export const makeProfileInfo = (
|
||||
profile: passport.Profile,
|
||||
params: any,
|
||||
idToken?: string,
|
||||
): ProfileInfo => {
|
||||
const { displayName: name } = profile;
|
||||
const { displayName } = profile;
|
||||
|
||||
let email = '';
|
||||
if (profile.emails) {
|
||||
let email: string | undefined = undefined;
|
||||
if (profile.emails && profile.emails.length > 0) {
|
||||
const [firstEmail] = profile.emails;
|
||||
email = firstEmail.value;
|
||||
}
|
||||
|
||||
if (!email && params.id_token) {
|
||||
try {
|
||||
const decoded: { email: string } = jwtDecoder(params.id_token);
|
||||
email = decoded.email;
|
||||
} catch (e) {
|
||||
console.error('Failed to parse id token and get profile info');
|
||||
}
|
||||
}
|
||||
|
||||
let picture = '';
|
||||
let picture: string | undefined = undefined;
|
||||
if (profile.photos) {
|
||||
const [firstPhoto] = profile.photos;
|
||||
picture = firstPhoto.value;
|
||||
}
|
||||
|
||||
if ((!email || !picture) && idToken) {
|
||||
try {
|
||||
const decoded: Record<string, string> = jwtDecoder(idToken);
|
||||
|
||||
if (!email && decoded.email) {
|
||||
email = decoded.email;
|
||||
}
|
||||
if (!picture && decoded.picture) {
|
||||
picture = decoded.picture;
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to parse id token and get profile info, ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!email) {
|
||||
throw new Error('No email received in profile info');
|
||||
}
|
||||
|
||||
return {
|
||||
name,
|
||||
email,
|
||||
picture,
|
||||
displayName,
|
||||
};
|
||||
};
|
||||
|
||||
export const executeRedirectStrategy = async (
|
||||
req: express.Request,
|
||||
providerStrategy: passport.Strategy,
|
||||
options: any,
|
||||
options: Record<string, string>,
|
||||
): Promise<RedirectInfo> => {
|
||||
return new Promise(resolve => {
|
||||
const strategy = Object.create(providerStrategy);
|
||||
@@ -73,30 +83,32 @@ export const executeRedirectStrategy = async (
|
||||
});
|
||||
};
|
||||
|
||||
export const executeFrameHandlerStrategy = async (
|
||||
export const executeFrameHandlerStrategy = async <T, PrivateInfo = never>(
|
||||
req: express.Request,
|
||||
providerStrategy: passport.Strategy,
|
||||
) => {
|
||||
return new Promise<{ user: any; info: any }>((resolve, reject) => {
|
||||
const strategy = Object.create(providerStrategy);
|
||||
strategy.success = (user: any, info: any) => {
|
||||
resolve({ user, info });
|
||||
};
|
||||
strategy.fail = (
|
||||
info: { type: 'success' | 'error'; message?: string },
|
||||
// _status: number,
|
||||
) => {
|
||||
reject(new Error(`Authentication rejected, ${info.message ?? ''}`));
|
||||
};
|
||||
strategy.error = (error: Error) => {
|
||||
reject(new Error(`Authentication failed, ${error}`));
|
||||
};
|
||||
strategy.redirect = () => {
|
||||
reject(new Error('Unexpected redirect'));
|
||||
};
|
||||
return new Promise<{ response: T; privateInfo: PrivateInfo }>(
|
||||
(resolve, reject) => {
|
||||
const strategy = Object.create(providerStrategy);
|
||||
strategy.success = (response: any, privateInfo: any) => {
|
||||
resolve({ response, privateInfo });
|
||||
};
|
||||
strategy.fail = (
|
||||
info: { type: 'success' | 'error'; message?: string },
|
||||
// _status: number,
|
||||
) => {
|
||||
reject(new Error(`Authentication rejected, ${info.message ?? ''}`));
|
||||
};
|
||||
strategy.error = (error: Error) => {
|
||||
reject(new Error(`Authentication failed, ${error}`));
|
||||
};
|
||||
strategy.redirect = () => {
|
||||
reject(new Error('Unexpected redirect'));
|
||||
};
|
||||
|
||||
strategy.authenticate(req, {});
|
||||
});
|
||||
strategy.authenticate(req, {});
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export const executeRefreshTokenStrategy = async (
|
||||
@@ -151,7 +163,7 @@ export const executeRefreshTokenStrategy = async (
|
||||
export const executeFetchUserProfileStrategy = async (
|
||||
providerStrategy: passport.Strategy,
|
||||
accessToken: string,
|
||||
params: any,
|
||||
idToken?: string,
|
||||
): Promise<ProfileInfo> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const anyStrategy = (providerStrategy as unknown) as ProviderStrategy;
|
||||
@@ -162,7 +174,7 @@ export const executeFetchUserProfileStrategy = async (
|
||||
reject(error);
|
||||
}
|
||||
|
||||
const profile = makeProfileInfo(passportProfile, params);
|
||||
const profile = makeProfileInfo(passportProfile, idToken);
|
||||
resolve(profile);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -19,16 +19,17 @@ import { Strategy as GithubStrategy } from 'passport-github2';
|
||||
import {
|
||||
executeFrameHandlerStrategy,
|
||||
executeRedirectStrategy,
|
||||
makeProfileInfo,
|
||||
} from '../../lib/PassportStrategyHelper';
|
||||
import {
|
||||
OAuthProviderHandlers,
|
||||
AuthProviderConfig,
|
||||
RedirectInfo,
|
||||
AuthInfoBase,
|
||||
AuthInfoPrivate,
|
||||
EnvironmentProviderConfig,
|
||||
OAuthProviderOptions,
|
||||
OAuthProviderConfig,
|
||||
OAuthResponse,
|
||||
PassportDoneCallback,
|
||||
} from '../types';
|
||||
import { OAuthProvider } from '../../lib/OAuthProvider';
|
||||
import {
|
||||
@@ -44,25 +45,42 @@ export class GithubAuthProvider implements OAuthProviderHandlers {
|
||||
constructor(options: OAuthProviderOptions) {
|
||||
this._strategy = new GithubStrategy(
|
||||
{ ...options },
|
||||
(accessToken: any, _: any, params: any, profile: any, done: any) => {
|
||||
(
|
||||
accessToken: any,
|
||||
_: any,
|
||||
params: any,
|
||||
rawProfile: any,
|
||||
done: PassportDoneCallback<OAuthResponse>,
|
||||
) => {
|
||||
const profile = makeProfileInfo(rawProfile);
|
||||
done(undefined, {
|
||||
providerInfo: {
|
||||
accessToken,
|
||||
scope: params.scope,
|
||||
expiresInSeconds: params.expires_in,
|
||||
},
|
||||
profile,
|
||||
accessToken,
|
||||
scope: params.scope,
|
||||
expiresInSeconds: params.expires_in,
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async start(req: express.Request, options: any): Promise<RedirectInfo> {
|
||||
async start(
|
||||
req: express.Request,
|
||||
options: Record<string, string>,
|
||||
): Promise<RedirectInfo> {
|
||||
return await executeRedirectStrategy(req, this._strategy, options);
|
||||
}
|
||||
|
||||
async handler(
|
||||
req: express.Request,
|
||||
): Promise<{ user: AuthInfoBase; info: AuthInfoPrivate }> {
|
||||
return await executeFrameHandlerStrategy(req, this._strategy);
|
||||
async handler(req: express.Request): Promise<{ response: OAuthResponse }> {
|
||||
const result = await executeFrameHandlerStrategy<OAuthResponse>(
|
||||
req,
|
||||
this._strategy,
|
||||
);
|
||||
|
||||
return {
|
||||
response: result.response,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,14 +25,13 @@ import {
|
||||
} from '../../lib/PassportStrategyHelper';
|
||||
import {
|
||||
OAuthProviderHandlers,
|
||||
AuthInfoBase,
|
||||
AuthInfoPrivate,
|
||||
RedirectInfo,
|
||||
AuthProviderConfig,
|
||||
AuthInfoWithProfile,
|
||||
EnvironmentProviderConfig,
|
||||
OAuthProviderOptions,
|
||||
OAuthProviderConfig,
|
||||
OAuthResponse,
|
||||
PassportDoneCallback,
|
||||
} from '../types';
|
||||
import { OAuthProvider } from '../../lib/OAuthProvider';
|
||||
import passport from 'passport';
|
||||
@@ -43,6 +42,10 @@ import {
|
||||
import { Logger } from 'winston';
|
||||
import { TokenIssuer } from '../../identity';
|
||||
|
||||
type PrivateInfo = {
|
||||
refreshToken: string;
|
||||
};
|
||||
|
||||
export class GoogleAuthProvider implements OAuthProviderHandlers {
|
||||
private readonly _strategy: GoogleStrategy;
|
||||
|
||||
@@ -56,18 +59,20 @@ export class GoogleAuthProvider implements OAuthProviderHandlers {
|
||||
accessToken: any,
|
||||
refreshToken: any,
|
||||
params: any,
|
||||
profile: passport.Profile,
|
||||
done: any,
|
||||
rawProfile: passport.Profile,
|
||||
done: PassportDoneCallback<OAuthResponse, PrivateInfo>,
|
||||
) => {
|
||||
const profileInfo = makeProfileInfo(profile, params);
|
||||
const profile = makeProfileInfo(rawProfile, params.id_token);
|
||||
done(
|
||||
undefined,
|
||||
{
|
||||
profile: profileInfo,
|
||||
idToken: params.id_token,
|
||||
accessToken,
|
||||
scope: params.scope,
|
||||
expiresInSeconds: params.expires_in,
|
||||
providerInfo: {
|
||||
idToken: params.id_token,
|
||||
accessToken,
|
||||
scope: params.scope,
|
||||
expiresInSeconds: params.expires_in,
|
||||
},
|
||||
profile,
|
||||
},
|
||||
{
|
||||
refreshToken,
|
||||
@@ -77,20 +82,33 @@ export class GoogleAuthProvider implements OAuthProviderHandlers {
|
||||
);
|
||||
}
|
||||
|
||||
async start(req: express.Request, options: any): Promise<RedirectInfo> {
|
||||
return await executeRedirectStrategy(req, this._strategy, options);
|
||||
async start(
|
||||
req: express.Request,
|
||||
options: Record<string, string>,
|
||||
): Promise<RedirectInfo> {
|
||||
const providerOptions = {
|
||||
...options,
|
||||
accessType: 'offline',
|
||||
prompt: 'consent',
|
||||
};
|
||||
return await executeRedirectStrategy(req, this._strategy, providerOptions);
|
||||
}
|
||||
|
||||
async handler(
|
||||
req: express.Request,
|
||||
): Promise<{ user: AuthInfoBase; info: AuthInfoPrivate }> {
|
||||
return await executeFrameHandlerStrategy(req, this._strategy);
|
||||
): Promise<{ response: OAuthResponse; refreshToken: string }> {
|
||||
const result = await executeFrameHandlerStrategy<
|
||||
OAuthResponse,
|
||||
PrivateInfo
|
||||
>(req, this._strategy);
|
||||
|
||||
return {
|
||||
response: result.response,
|
||||
refreshToken: result.privateInfo.refreshToken,
|
||||
};
|
||||
}
|
||||
|
||||
async refresh(
|
||||
refreshToken: string,
|
||||
scope: string,
|
||||
): Promise<AuthInfoWithProfile> {
|
||||
async refresh(refreshToken: string, scope: string): Promise<OAuthResponse> {
|
||||
const { accessToken, params } = await executeRefreshTokenStrategy(
|
||||
this._strategy,
|
||||
refreshToken,
|
||||
@@ -100,14 +118,16 @@ export class GoogleAuthProvider implements OAuthProviderHandlers {
|
||||
const profile = await executeFetchUserProfileStrategy(
|
||||
this._strategy,
|
||||
accessToken,
|
||||
params,
|
||||
params.id_token,
|
||||
);
|
||||
|
||||
return {
|
||||
accessToken,
|
||||
idToken: params.id_token,
|
||||
expiresInSeconds: params.expires_in,
|
||||
scope: params.scope,
|
||||
providerInfo: {
|
||||
accessToken,
|
||||
idToken: params.id_token,
|
||||
expiresInSeconds: params.expires_in,
|
||||
scope: params.scope,
|
||||
},
|
||||
profile,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,7 +15,11 @@
|
||||
*/
|
||||
|
||||
import express from 'express';
|
||||
import { Strategy as SamlStrategy } from 'passport-saml';
|
||||
import {
|
||||
Strategy as SamlStrategy,
|
||||
Profile as SamlProfile,
|
||||
VerifyWithoutRequest,
|
||||
} from 'passport-saml';
|
||||
import {
|
||||
executeFrameHandlerStrategy,
|
||||
executeRedirectStrategy,
|
||||
@@ -25,6 +29,8 @@ import {
|
||||
AuthProviderRouteHandlers,
|
||||
EnvironmentProviderConfig,
|
||||
SAMLProviderConfig,
|
||||
PassportDoneCallback,
|
||||
ProfileInfo,
|
||||
} from '../types';
|
||||
import { postMessageResponse } from '../../lib/OAuthProvider';
|
||||
import {
|
||||
@@ -32,30 +38,39 @@ import {
|
||||
EnvironmentHandler,
|
||||
} from '../../lib/EnvironmentHandler';
|
||||
import { Logger } from 'winston';
|
||||
import { TokenIssuer } from '../../identity';
|
||||
|
||||
type SamlInfo = {
|
||||
userId: string;
|
||||
profile: ProfileInfo;
|
||||
};
|
||||
|
||||
export class SamlAuthProvider implements AuthProviderRouteHandlers {
|
||||
private readonly strategy: SamlStrategy;
|
||||
private readonly tokenIssuer: TokenIssuer;
|
||||
|
||||
constructor(options: SAMLProviderOptions) {
|
||||
this.strategy = new SamlStrategy(
|
||||
{ ...options },
|
||||
(profile: any, done: any) => {
|
||||
// TODO: There's plenty more validation and profile handling to do here,
|
||||
// this provider is currently only intended to validate the provider pattern
|
||||
// for non-oauth auth flows.
|
||||
// TODO: This flow doesn't issue an identity token that can be used to validate
|
||||
// the identity of the user in other backends, which we need in some form.
|
||||
done(undefined, {
|
||||
email: profile.email,
|
||||
firstName: profile.firstName,
|
||||
lastName: profile.lastName,
|
||||
displayName: profile.displayName,
|
||||
});
|
||||
},
|
||||
);
|
||||
this.tokenIssuer = options.tokenIssuer;
|
||||
this.strategy = new SamlStrategy({ ...options }, ((
|
||||
profile: SamlProfile,
|
||||
done: PassportDoneCallback<SamlInfo>,
|
||||
) => {
|
||||
// TODO: There's plenty more validation and profile handling to do here,
|
||||
// this provider is currently only intended to validate the provider pattern
|
||||
// for non-oauth auth flows.
|
||||
// TODO: This flow doesn't issue an identity token that can be used to validate
|
||||
// the identity of the user in other backends, which we need in some form.
|
||||
done(undefined, {
|
||||
userId: profile.ID!,
|
||||
profile: {
|
||||
email: profile.email!,
|
||||
displayName: profile.displayName as string,
|
||||
},
|
||||
});
|
||||
}) as VerifyWithoutRequest);
|
||||
}
|
||||
|
||||
async start(req: express.Request, res: express.Response): Promise<any> {
|
||||
async start(req: express.Request, res: express.Response): Promise<void> {
|
||||
const { url } = await executeRedirectStrategy(req, this.strategy, {});
|
||||
res.redirect(url);
|
||||
}
|
||||
@@ -63,17 +78,28 @@ export class SamlAuthProvider implements AuthProviderRouteHandlers {
|
||||
async frameHandler(
|
||||
req: express.Request,
|
||||
res: express.Response,
|
||||
): Promise<any> {
|
||||
): Promise<void> {
|
||||
try {
|
||||
const { user } = await executeFrameHandlerStrategy(req, this.strategy);
|
||||
const {
|
||||
response: { userId, profile },
|
||||
} = await executeFrameHandlerStrategy<SamlInfo>(req, this.strategy);
|
||||
|
||||
const id = userId;
|
||||
const idToken = await this.tokenIssuer.issueToken({
|
||||
claims: { sub: id },
|
||||
});
|
||||
|
||||
return postMessageResponse(res, 'http://localhost:3000', {
|
||||
type: 'auth-result',
|
||||
payload: user,
|
||||
type: 'authorization_response',
|
||||
response: {
|
||||
providerInfo: {},
|
||||
profile,
|
||||
backstageIdentity: { id, idToken },
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
return postMessageResponse(res, 'http://localhost:3000', {
|
||||
type: 'auth-result',
|
||||
type: 'authorization_response',
|
||||
error: {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
@@ -82,7 +108,7 @@ export class SamlAuthProvider implements AuthProviderRouteHandlers {
|
||||
}
|
||||
}
|
||||
|
||||
async logout(_req: express.Request, res: express.Response): Promise<any> {
|
||||
async logout(_req: express.Request, res: express.Response): Promise<void> {
|
||||
res.send('noop');
|
||||
}
|
||||
}
|
||||
@@ -91,12 +117,14 @@ type SAMLProviderOptions = {
|
||||
entryPoint: string;
|
||||
issuer: string;
|
||||
path: string;
|
||||
tokenIssuer: TokenIssuer;
|
||||
};
|
||||
|
||||
export function createSamlProvider(
|
||||
_authProviderConfig: AuthProviderConfig,
|
||||
providerConfig: EnvironmentProviderConfig,
|
||||
logger: Logger,
|
||||
tokenIssuer: TokenIssuer,
|
||||
) {
|
||||
const envProviders: EnvironmentHandlers = {};
|
||||
|
||||
@@ -106,6 +134,7 @@ export function createSamlProvider(
|
||||
entryPoint: config.entryPoint,
|
||||
issuer: config.issuer,
|
||||
path: '/auth/saml/handler/frame',
|
||||
tokenIssuer,
|
||||
};
|
||||
|
||||
if (!opts.entryPoint || !opts.issuer) {
|
||||
|
||||
@@ -89,25 +89,30 @@ export interface OAuthProviderHandlers {
|
||||
* @param {express.Request} req
|
||||
* @param options
|
||||
*/
|
||||
start(req: express.Request, options: any): Promise<any>;
|
||||
start(
|
||||
req: express.Request,
|
||||
options: Record<string, string>,
|
||||
): Promise<RedirectInfo>;
|
||||
|
||||
/**
|
||||
* Handles the redirect from the auth provider when the user has signed in.
|
||||
* @param {express.Request} req
|
||||
*/
|
||||
handler(req: express.Request): Promise<any>;
|
||||
handler(
|
||||
req: express.Request,
|
||||
): Promise<{ response: OAuthResponse; refreshToken?: string }>;
|
||||
|
||||
/**
|
||||
* (Optional) Given a refresh token and scope fetches a new access token from the auth provider.
|
||||
* @param {string} refreshToken
|
||||
* @param {string} scope
|
||||
*/
|
||||
refresh?(refreshToken: string, scope: string): Promise<any>;
|
||||
refresh?(refreshToken: string, scope: string): Promise<OAuthResponse>;
|
||||
|
||||
/**
|
||||
* (Optional) Sign out of the auth provider.
|
||||
*/
|
||||
logout?(): Promise<any>;
|
||||
logout?(): Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -134,7 +139,7 @@ export interface AuthProviderRouteHandlers {
|
||||
* @param {express.Request} req
|
||||
* @param {express.Response} res
|
||||
*/
|
||||
start(req: express.Request, res: express.Response): Promise<any>;
|
||||
start(req: express.Request, res: express.Response): Promise<void>;
|
||||
|
||||
/**
|
||||
* Once the user signs in or consents in the OAuth screen, the auth provider redirects to the
|
||||
@@ -149,7 +154,7 @@ export interface AuthProviderRouteHandlers {
|
||||
* @param {express.Request} req
|
||||
* @param {express.Response} res
|
||||
*/
|
||||
frameHandler(req: express.Request, res: express.Response): Promise<any>;
|
||||
frameHandler(req: express.Request, res: express.Response): Promise<void>;
|
||||
|
||||
/**
|
||||
* (Optional) If the auth provider supports refresh tokens then this method handles
|
||||
@@ -163,7 +168,7 @@ export interface AuthProviderRouteHandlers {
|
||||
* @param {express.Request} req
|
||||
* @param {express.Response} res
|
||||
*/
|
||||
refresh?(req: express.Request, res: express.Response): Promise<any>;
|
||||
refresh?(req: express.Request, res: express.Response): Promise<void>;
|
||||
|
||||
/**
|
||||
* (Optional) Handles sign out requests
|
||||
@@ -174,7 +179,7 @@ export interface AuthProviderRouteHandlers {
|
||||
* @param {express.Request} req
|
||||
* @param {express.Response} res
|
||||
*/
|
||||
logout?(req: express.Request, res: express.Response): Promise<any>;
|
||||
logout?(req: express.Request, res: express.Response): Promise<void>;
|
||||
}
|
||||
|
||||
export type AuthProviderFactory = (
|
||||
@@ -184,7 +189,30 @@ export type AuthProviderFactory = (
|
||||
issuer: TokenIssuer,
|
||||
) => AuthProviderRouteHandlers;
|
||||
|
||||
export type AuthInfoBase = {
|
||||
export type AuthResponse<ProviderInfo> = {
|
||||
providerInfo: ProviderInfo;
|
||||
profile: ProfileInfo;
|
||||
backstageIdentity: BackstageIdentity;
|
||||
};
|
||||
|
||||
export type OAuthResponse = Omit<
|
||||
AuthResponse<OAuthProviderInfo>,
|
||||
'backstageIdentity'
|
||||
>;
|
||||
|
||||
export type BackstageIdentity = {
|
||||
/**
|
||||
* The backstage user ID.
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* An ID token that can be used to authenticate the user within Backstage.
|
||||
*/
|
||||
idToken: string;
|
||||
};
|
||||
|
||||
export type OAuthProviderInfo = {
|
||||
/**
|
||||
* An access token issued for the signed in user.
|
||||
*/
|
||||
@@ -203,14 +231,7 @@ export type AuthInfoBase = {
|
||||
scope: string;
|
||||
};
|
||||
|
||||
export type AuthInfoWithProfile = AuthInfoBase & {
|
||||
/**
|
||||
* Profile information of the signed in user.
|
||||
*/
|
||||
profile: ProfileInfo | undefined;
|
||||
};
|
||||
|
||||
export type AuthInfoPrivate = {
|
||||
export type OAuthPrivateInfo = {
|
||||
/**
|
||||
* A refresh token issued for the signed in user.
|
||||
*/
|
||||
@@ -221,16 +242,22 @@ export type AuthInfoPrivate = {
|
||||
* Payload sent as a post message after the auth request is complete.
|
||||
* If successful then has a valid payload with Auth information else contains an error.
|
||||
*/
|
||||
export type AuthResponse =
|
||||
export type WebMessageResponse =
|
||||
| {
|
||||
type: 'auth-result';
|
||||
payload: AuthInfoBase | AuthInfoWithProfile;
|
||||
type: 'authorization_response';
|
||||
response: AuthResponse<unknown>;
|
||||
}
|
||||
| {
|
||||
type: 'auth-result';
|
||||
type: 'authorization_response';
|
||||
error: Error;
|
||||
};
|
||||
|
||||
export type PassportDoneCallback<Res, Private = never> = (
|
||||
err?: Error,
|
||||
response?: Res,
|
||||
privateInfo?: Private,
|
||||
) => void;
|
||||
|
||||
export type RedirectInfo = {
|
||||
/**
|
||||
* URL to redirect to
|
||||
@@ -242,6 +269,12 @@ export type RedirectInfo = {
|
||||
status?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Used to display login information to user, i.e. sidebar popup.
|
||||
*
|
||||
* It is also temporarily used as the profile of the signed-in user's Backstage
|
||||
* identity, but we want to replace that with data from identity and/org catalog service
|
||||
*/
|
||||
export type ProfileInfo = {
|
||||
/**
|
||||
* Email ID of the signed in user.
|
||||
@@ -250,12 +283,12 @@ export type ProfileInfo = {
|
||||
/**
|
||||
* Display name that can be presented to the signed in user.
|
||||
*/
|
||||
name: string;
|
||||
displayName?: string;
|
||||
/**
|
||||
* URL to an image that can be used as the display image or avatar of the
|
||||
* signed in user.
|
||||
*/
|
||||
picture: string;
|
||||
picture?: string;
|
||||
};
|
||||
|
||||
export type RefreshTokenResponse = {
|
||||
|
||||
@@ -67,12 +67,6 @@ export async function createRouter(
|
||||
clientId: process.env.AUTH_GOOGLE_CLIENT_ID!,
|
||||
clientSecret: process.env.AUTH_GOOGLE_CLIENT_SECRET!,
|
||||
},
|
||||
production: {
|
||||
appOrigin: 'http://localhost:3000',
|
||||
secure: false,
|
||||
clientId: '',
|
||||
clientSecret: '',
|
||||
},
|
||||
},
|
||||
github: {
|
||||
development: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-catalog-backend",
|
||||
"version": "0.1.1-alpha.10",
|
||||
"version": "0.1.1-alpha.12",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -21,8 +21,8 @@
|
||||
"mock-data": "./scripts/mock-data"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.1.1-alpha.10",
|
||||
"@backstage/catalog-model": "^0.1.1-alpha.10",
|
||||
"@backstage/backend-common": "^0.1.1-alpha.12",
|
||||
"@backstage/catalog-model": "^0.1.1-alpha.12",
|
||||
"@types/express": "^4.17.6",
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^3.0.3",
|
||||
@@ -36,10 +36,10 @@
|
||||
"winston": "^3.2.1",
|
||||
"yaml": "^1.9.2",
|
||||
"yn": "^4.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/lodash": "^4.14.151",
|
||||
"@types/node-fetch": "^2.5.7",
|
||||
"@types/supertest": "^2.0.8",
|
||||
@@ -49,7 +49,7 @@
|
||||
"supertest": "^4.0.2"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*.{js,d.ts}",
|
||||
"dist",
|
||||
"migrations/**/*.{js,d.ts}"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -14,5 +14,5 @@ for URL in \
|
||||
--location \
|
||||
--request POST 'localhost:7000/catalog/locations' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw "{\"type\": \"github\", \"target\": \"https://github.com/spotify/backstage/blob/master/plugins/catalog-backend/examples/${URL}\"}"
|
||||
--data-raw "{\"type\": \"github\", \"target\": \"https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/${URL}\"}"
|
||||
done
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-catalog",
|
||||
"version": "0.1.1-alpha.10",
|
||||
"version": "0.1.1-alpha.12",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -21,11 +21,11 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.1.1-alpha.10",
|
||||
"@backstage/core": "^0.1.1-alpha.10",
|
||||
"@backstage/plugin-scaffolder": "^0.1.1-alpha.10",
|
||||
"@backstage/plugin-sentry": "^0.1.1-alpha.10",
|
||||
"@backstage/theme": "^0.1.1-alpha.10",
|
||||
"@backstage/catalog-model": "^0.1.1-alpha.12",
|
||||
"@backstage/core": "^0.1.1-alpha.12",
|
||||
"@backstage/plugin-scaffolder": "^0.1.1-alpha.12",
|
||||
"@backstage/plugin-sentry": "^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",
|
||||
@@ -38,9 +38,9 @@
|
||||
"swr": "^0.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.10",
|
||||
"@backstage/dev-utils": "^0.1.1-alpha.10",
|
||||
"@backstage/test-utils": "^0.1.1-alpha.10",
|
||||
"@backstage/cli": "^0.1.1-alpha.12",
|
||||
"@backstage/dev-utils": "^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/react-hooks": "^3.3.0",
|
||||
@@ -54,6 +54,6 @@
|
||||
"whatwg-fetch": "^3.0.0"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*.{js,d.ts}"
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -19,23 +19,24 @@ import {
|
||||
Content,
|
||||
errorApiRef,
|
||||
Header,
|
||||
HeaderLabel,
|
||||
HeaderTabs,
|
||||
Page,
|
||||
pageTheme,
|
||||
PageTheme,
|
||||
Progress,
|
||||
useApi,
|
||||
PageTheme,
|
||||
} from '@backstage/core';
|
||||
import { SentryIssuesWidget } from '@backstage/plugin-sentry';
|
||||
import { Grid } from '@material-ui/core';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import React, { FC, useEffect, useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { useAsync } from 'react-use';
|
||||
import { catalogApiRef } from '../..';
|
||||
import { EntityContextMenu } from '../EntityContextMenu/EntityContextMenu';
|
||||
import { EntityMetadataCard } from '../EntityMetadataCard/EntityMetadataCard';
|
||||
import { UnregisterEntityDialog } from '../UnregisterEntityDialog/UnregisterEntityDialog';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
|
||||
const REDIRECT_DELAY = 1000;
|
||||
function headerProps(
|
||||
@@ -138,7 +139,19 @@ export const EntityPage: FC<{}> = () => {
|
||||
return (
|
||||
<Page theme={getPageTheme(entity)}>
|
||||
<Header title={headerTitle} type={headerType}>
|
||||
{entity && <EntityContextMenu onUnregisterEntity={showRemovalDialog} />}
|
||||
{entity && (
|
||||
<>
|
||||
<HeaderLabel
|
||||
label="Owner"
|
||||
value={entity.spec?.owner || 'unknown'}
|
||||
/>
|
||||
<HeaderLabel
|
||||
label="Lifecycle"
|
||||
value={entity.spec?.lifecycle || 'unknown'}
|
||||
/>
|
||||
<EntityContextMenu onUnregisterEntity={showRemovalDialog} />
|
||||
</>
|
||||
)}
|
||||
</Header>
|
||||
|
||||
{loading && <Progress />}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-circleci",
|
||||
"version": "0.1.1-alpha.10",
|
||||
"version": "0.1.1-alpha.12",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -30,8 +30,8 @@
|
||||
"postpack": "backstage-cli postpack"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core": "^0.1.1-alpha.10",
|
||||
"@backstage/theme": "^0.1.1-alpha.10",
|
||||
"@backstage/core": "^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",
|
||||
@@ -45,8 +45,8 @@
|
||||
"react-use": "^14.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.10",
|
||||
"@backstage/dev-utils": "^0.1.1-alpha.10",
|
||||
"@backstage/cli": "^0.1.1-alpha.12",
|
||||
"@backstage/dev-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",
|
||||
@@ -57,6 +57,6 @@
|
||||
"jest-fetch-mock": "^3.0.3"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*.{js,d.ts}"
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-explore",
|
||||
"version": "0.1.1-alpha.10",
|
||||
"version": "0.1.1-alpha.12",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -21,8 +21,8 @@
|
||||
"start": "backstage-cli plugin:serve"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core": "^0.1.1-alpha.10",
|
||||
"@backstage/theme": "^0.1.1-alpha.10",
|
||||
"@backstage/core": "^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",
|
||||
@@ -32,9 +32,9 @@
|
||||
"react-use": "^14.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.10",
|
||||
"@backstage/dev-utils": "^0.1.1-alpha.10",
|
||||
"@backstage/test-utils": "^0.1.1-alpha.10",
|
||||
"@backstage/cli": "^0.1.1-alpha.12",
|
||||
"@backstage/dev-utils": "^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",
|
||||
@@ -44,6 +44,6 @@
|
||||
"jest-fetch-mock": "^3.0.3"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*.{js,d.ts}"
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-gitops-profiles",
|
||||
"version": "0.1.1-alpha.10",
|
||||
"version": "0.1.1-alpha.12",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -21,8 +21,8 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core": "^0.1.1-alpha.10",
|
||||
"@backstage/theme": "^0.1.1-alpha.10",
|
||||
"@backstage/core": "^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",
|
||||
@@ -32,8 +32,8 @@
|
||||
"react-use": "^14.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.10",
|
||||
"@backstage/dev-utils": "^0.1.1-alpha.10",
|
||||
"@backstage/cli": "^0.1.1-alpha.12",
|
||||
"@backstage/dev-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",
|
||||
@@ -43,6 +43,6 @@
|
||||
"jest-fetch-mock": "^3.0.3"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*.{js,d.ts}"
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -15,15 +15,25 @@
|
||||
*/
|
||||
|
||||
import { createDevApp } from '@backstage/dev-utils';
|
||||
import { githubAuthApiRef, errorApiRef } from '@backstage/core';
|
||||
import { plugin, GraphQLEndpoints, graphQlBrowseApiRef } from '../src';
|
||||
|
||||
createDevApp()
|
||||
.registerPlugin(plugin)
|
||||
.registerApiFactory({
|
||||
implements: graphQlBrowseApiRef,
|
||||
deps: {},
|
||||
factory() {
|
||||
deps: {
|
||||
errorApi: errorApiRef,
|
||||
githubAuthApi: githubAuthApiRef,
|
||||
},
|
||||
factory({ errorApi, githubAuthApi }) {
|
||||
return GraphQLEndpoints.from([
|
||||
GraphQLEndpoints.github({
|
||||
id: 'github',
|
||||
title: 'GitHub',
|
||||
errorApi,
|
||||
githubAuthApi,
|
||||
}),
|
||||
GraphQLEndpoints.create({
|
||||
id: 'gitlab',
|
||||
title: 'GitLab',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/plugin-graphiql",
|
||||
"description": "Backstage plugin for browsing GraphQL APIs",
|
||||
"version": "0.1.1-alpha.10",
|
||||
"version": "0.1.1-alpha.12",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -31,8 +31,8 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core": "^0.1.1-alpha.10",
|
||||
"@backstage/theme": "^0.1.1-alpha.10",
|
||||
"@backstage/core": "^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",
|
||||
@@ -43,9 +43,9 @@
|
||||
"react-use": "^14.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.10",
|
||||
"@backstage/dev-utils": "^0.1.1-alpha.10",
|
||||
"@backstage/test-utils": "^0.1.1-alpha.10",
|
||||
"@backstage/cli": "^0.1.1-alpha.12",
|
||||
"@backstage/dev-utils": "^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",
|
||||
@@ -57,6 +57,6 @@
|
||||
"react-router-dom": "6.0.0-alpha.5"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*.{js,d.ts}"
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,64 +1,19 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<g id="surface1">
|
||||
<path
|
||||
fill="#E535AB"
|
||||
d="M 3.449219 18.160156 L 2.585938 17.660156 L 12.195312 1.019531 L 13.058594 1.515625 Z M 3.449219 18.160156 "
|
||||
/>
|
||||
<path
|
||||
fill="#E535AB"
|
||||
d="M 2.386719 16.332031 L 21.605469 16.332031 L 21.605469 17.328125 L 2.386719 17.328125 Z M 2.386719 16.332031 "
|
||||
/>
|
||||
<path
|
||||
fill="#E535AB"
|
||||
d="M 12.382812 22.441406 L 2.769531 16.890625 L 3.265625 16.027344 L 12.878906 21.578125 Z M 12.382812 22.441406 "
|
||||
/>
|
||||
<path
|
||||
fill="#E535AB"
|
||||
d="M 20.730469 7.976562 L 11.117188 2.425781 L 11.617188 1.5625 L 21.230469 7.113281 Z M 20.730469 7.976562 "
|
||||
/>
|
||||
<path
|
||||
fill="#E535AB"
|
||||
d="M 3.269531 7.972656 L 2.769531 7.109375 L 12.382812 1.558594 L 12.882812 2.421875 Z M 3.269531 7.972656 "
|
||||
/>
|
||||
<path
|
||||
fill="#E535AB"
|
||||
d="M 20.554688 18.160156 L 10.945312 1.515625 L 11.808594 1.019531 L 21.417969 17.660156 Z M 20.554688 18.160156 "
|
||||
/>
|
||||
<path
|
||||
fill="#E535AB"
|
||||
d="M 3.148438 6.449219 L 4.144531 6.449219 L 4.144531 17.550781 L 3.148438 17.550781 Z M 3.148438 6.449219 "
|
||||
/>
|
||||
<path
|
||||
fill="#E535AB"
|
||||
d="M 19.855469 6.449219 L 20.851562 6.449219 L 20.851562 17.550781 L 19.855469 17.550781 Z M 19.855469 6.449219 "
|
||||
/>
|
||||
<path
|
||||
fill="#E535AB"
|
||||
d="M 12.210938 22.019531 L 11.777344 21.265625 L 20.136719 16.441406 L 20.570312 17.191406 Z M 12.210938 22.019531 "
|
||||
/>
|
||||
<path
|
||||
fill="#E535AB"
|
||||
d="M 22.171875 17.875 C 21.59375 18.875 20.308594 19.21875 19.308594 18.640625 C 18.304688 18.066406 17.964844 16.78125 18.539062 15.78125 C 19.117188 14.777344 20.398438 14.4375 21.402344 15.011719 C 22.410156 15.59375 22.753906 16.871094 22.171875 17.875 "
|
||||
/>
|
||||
<path
|
||||
fill="#E535AB"
|
||||
d="M 5.453125 8.21875 C 4.878906 9.222656 3.59375 9.5625 2.59375 8.988281 C 1.589844 8.410156 1.246094 7.128906 1.824219 6.125 C 2.398438 5.125 3.683594 4.78125 4.6875 5.359375 C 5.6875 5.941406 6.03125 7.21875 5.453125 8.21875 "
|
||||
/>
|
||||
<path
|
||||
fill="#E535AB"
|
||||
d="M 1.828125 17.875 C 1.253906 16.871094 1.597656 15.59375 2.597656 15.011719 C 3.601562 14.4375 4.878906 14.777344 5.460938 15.78125 C 6.035156 16.78125 5.695312 18.058594 4.691406 18.640625 C 3.683594 19.21875 2.40625 18.875 1.828125 17.875 "
|
||||
/>
|
||||
<path
|
||||
fill="#E535AB"
|
||||
d="M 18.546875 8.21875 C 17.96875 7.21875 18.3125 5.941406 19.3125 5.359375 C 20.316406 4.78125 21.59375 5.125 22.175781 6.125 C 22.753906 7.128906 22.410156 8.40625 21.40625 8.988281 C 20.40625 9.5625 19.121094 9.222656 18.546875 8.21875 "
|
||||
/>
|
||||
<path
|
||||
fill="#E535AB"
|
||||
d="M 12 23.746094 C 10.84375 23.746094 9.90625 22.8125 9.90625 21.652344 C 9.90625 20.496094 10.84375 19.558594 12 19.558594 C 13.15625 19.558594 14.09375 20.496094 14.09375 21.652344 C 14.09375 22.804688 13.15625 23.746094 12 23.746094 "
|
||||
/>
|
||||
<path
|
||||
fill="#E535AB"
|
||||
d="M 12 4.441406 C 10.84375 4.441406 9.90625 3.503906 9.90625 2.347656 C 9.90625 1.1875 10.84375 0.253906 12 0.253906 C 13.15625 0.253906 14.09375 1.1875 14.09375 2.347656 C 14.09375 3.503906 13.15625 4.441406 12 4.441406 "
|
||||
/>
|
||||
<g>
|
||||
<path d="M 3.449219 18.160156 L 2.585938 17.660156 L 12.195312 1.019531 L 13.058594 1.515625 Z M 3.449219 18.160156" />
|
||||
<path d="M 2.386719 16.332031 L 21.605469 16.332031 L 21.605469 17.328125 L 2.386719 17.328125 Z M 2.386719 16.332031" />
|
||||
<path d="M 12.382812 22.441406 L 2.769531 16.890625 L 3.265625 16.027344 L 12.878906 21.578125 Z M 12.382812 22.441406" />
|
||||
<path d="M 20.730469 7.976562 L 11.117188 2.425781 L 11.617188 1.5625 L 21.230469 7.113281 Z M 20.730469 7.976562" />
|
||||
<path d="M 3.269531 7.972656 L 2.769531 7.109375 L 12.382812 1.558594 L 12.882812 2.421875 Z M 3.269531 7.972656" />
|
||||
<path d="M 20.554688 18.160156 L 10.945312 1.515625 L 11.808594 1.019531 L 21.417969 17.660156 Z M 20.554688 18.160156" />
|
||||
<path d="M 3.148438 6.449219 L 4.144531 6.449219 L 4.144531 17.550781 L 3.148438 17.550781 Z M 3.148438 6.449219" />
|
||||
<path d="M 19.855469 6.449219 L 20.851562 6.449219 L 20.851562 17.550781 L 19.855469 17.550781 Z M 19.855469 6.449219" />
|
||||
<path d="M 12.210938 22.019531 L 11.777344 21.265625 L 20.136719 16.441406 L 20.570312 17.191406 Z M 12.210938 22.019531" />
|
||||
<path d="M 22.171875 17.875 C 21.59375 18.875 20.308594 19.21875 19.308594 18.640625 C 18.304688 18.066406 17.964844 16.78125 18.539062 15.78125 C 19.117188 14.777344 20.398438 14.4375 21.402344 15.011719 C 22.410156 15.59375 22.753906 16.871094 22.171875 17.875" />
|
||||
<path d="M 5.453125 8.21875 C 4.878906 9.222656 3.59375 9.5625 2.59375 8.988281 C 1.589844 8.410156 1.246094 7.128906 1.824219 6.125 C 2.398438 5.125 3.683594 4.78125 4.6875 5.359375 C 5.6875 5.941406 6.03125 7.21875 5.453125 8.21875" />
|
||||
<path d="M 1.828125 17.875 C 1.253906 16.871094 1.597656 15.59375 2.597656 15.011719 C 3.601562 14.4375 4.878906 14.777344 5.460938 15.78125 C 6.035156 16.78125 5.695312 18.058594 4.691406 18.640625 C 3.683594 19.21875 2.40625 18.875 1.828125 17.875" />
|
||||
<path d="M 18.546875 8.21875 C 17.96875 7.21875 18.3125 5.941406 19.3125 5.359375 C 20.316406 4.78125 21.59375 5.125 22.175781 6.125 C 22.753906 7.128906 22.410156 8.40625 21.40625 8.988281 C 20.40625 9.5625 19.121094 9.222656 18.546875 8.21875" />
|
||||
<path d="M 12 23.746094 C 10.84375 23.746094 9.90625 22.8125 9.90625 21.652344 C 9.90625 20.496094 10.84375 19.558594 12 19.558594 C 13.15625 19.558594 14.09375 20.496094 14.09375 21.652344 C 14.09375 22.804688 13.15625 23.746094 12 23.746094" />
|
||||
<path d="M 12 4.441406 C 10.84375 4.441406 9.90625 3.503906 9.90625 2.347656 C 9.90625 1.1875 10.84375 0.253906 12 0.253906 C 13.15625 0.253906 14.09375 1.1875 14.09375 2.347656 C 14.09375 3.503906 13.15625 4.441406 12 4.441406" />
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 2.7 KiB |
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import { GraphQLBrowseApi, GraphQLEndpoint } from './types';
|
||||
import { ErrorApi, OAuthApi } from '@backstage/core';
|
||||
|
||||
// Helper for generic http endpoints
|
||||
export type EndpointConfig = {
|
||||
@@ -28,6 +29,23 @@ export type EndpointConfig = {
|
||||
headers?: { [name in string]: string };
|
||||
};
|
||||
|
||||
export type GithubEndpointConfig = {
|
||||
id: string;
|
||||
title: string;
|
||||
/**
|
||||
* Github GraphQL API url, defaults to https://api.github.com/graphql
|
||||
*/
|
||||
url?: string;
|
||||
/**
|
||||
* Errors will be posted to the ErrorApi if it is provided.
|
||||
*/
|
||||
errorApi?: ErrorApi;
|
||||
/**
|
||||
* GitHub Auth API used to authenticate requests.
|
||||
*/
|
||||
githubAuthApi: OAuthApi;
|
||||
};
|
||||
|
||||
export class GraphQLEndpoints implements GraphQLBrowseApi {
|
||||
// Create a support
|
||||
static create(config: EndpointConfig): GraphQLEndpoint {
|
||||
@@ -51,6 +69,84 @@ export class GraphQLEndpoints implements GraphQLBrowseApi {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a GitHub GraphQL endpoint that uses the GithubAuth API to authenticate requests.
|
||||
*
|
||||
* If a request requires more permissions than is granted by the existing session,
|
||||
* the fetcher will automatically ask for the additional scopes that are required.
|
||||
*/
|
||||
static github(config: GithubEndpointConfig): GraphQLEndpoint {
|
||||
const {
|
||||
id,
|
||||
title,
|
||||
url = 'https://api.github.com/graphql',
|
||||
errorApi,
|
||||
githubAuthApi,
|
||||
} = config;
|
||||
type ResponseBody = {
|
||||
errors?: Array<{ type: string; message: string }>;
|
||||
};
|
||||
|
||||
return {
|
||||
id,
|
||||
title,
|
||||
fetcher: async (params: any) => {
|
||||
let retried = false;
|
||||
|
||||
const doRequest = async (): Promise<any> => {
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${await githubAuthApi.getAccessToken()}`,
|
||||
},
|
||||
body: JSON.stringify(params),
|
||||
});
|
||||
if (!res.ok) {
|
||||
throw new Error(
|
||||
`Request failed with status ${res.status} ${res.statusText}`,
|
||||
);
|
||||
}
|
||||
const data = (await res.json()) as ResponseBody;
|
||||
|
||||
if (!data.errors || retried) {
|
||||
return data;
|
||||
}
|
||||
retried = true;
|
||||
|
||||
const missingScopes = data.errors
|
||||
.filter(({ type }) => type === 'INSUFFICIENT_SCOPES')
|
||||
.flatMap(({ message }) => {
|
||||
const scopesMatch = message.match(
|
||||
/one of the following scopes: (\[.*?\])/,
|
||||
);
|
||||
if (!scopesMatch) {
|
||||
return [];
|
||||
}
|
||||
try {
|
||||
const scopes = JSON.parse(scopesMatch[1].replace(/'/g, '"'));
|
||||
return scopes;
|
||||
} catch (error) {
|
||||
if (errorApi) {
|
||||
errorApi.post(
|
||||
new Error(
|
||||
`Failed to parse scope string "${scopesMatch[1]}", ${error}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
await githubAuthApi.getAccessToken(missingScopes);
|
||||
return doRequest();
|
||||
};
|
||||
|
||||
return await doRequest();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static from(endpoints: GraphQLEndpoint[]) {
|
||||
return new GraphQLEndpoints(endpoints);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-identity-backend",
|
||||
"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,7 +20,7 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.1.1-alpha.10",
|
||||
"@backstage/backend-common": "^0.1.1-alpha.12",
|
||||
"@types/express": "^4.17.6",
|
||||
"compression": "^1.7.4",
|
||||
"cors": "^2.8.5",
|
||||
@@ -33,10 +33,10 @@
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.10",
|
||||
"@backstage/cli": "^0.1.1-alpha.12",
|
||||
"jest-fetch-mock": "^3.0.3"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*.{js,d.ts}"
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-lighthouse",
|
||||
"version": "0.1.1-alpha.10",
|
||||
"version": "0.1.1-alpha.12",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -21,8 +21,8 @@
|
||||
"start": "backstage-cli plugin:serve"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core": "^0.1.1-alpha.10",
|
||||
"@backstage/theme": "^0.1.1-alpha.10",
|
||||
"@backstage/core": "^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",
|
||||
@@ -33,9 +33,9 @@
|
||||
"react-use": "^14.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.10",
|
||||
"@backstage/dev-utils": "^0.1.1-alpha.10",
|
||||
"@backstage/test-utils": "^0.1.1-alpha.10",
|
||||
"@backstage/cli": "^0.1.1-alpha.12",
|
||||
"@backstage/dev-utils": "^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",
|
||||
@@ -45,6 +45,6 @@
|
||||
"jest-fetch-mock": "^3.0.3"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*.{js,d.ts}"
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-register-component",
|
||||
"version": "0.1.1-alpha.10",
|
||||
"version": "0.1.1-alpha.12",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -21,10 +21,10 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.1.1-alpha.10",
|
||||
"@backstage/core": "^0.1.1-alpha.10",
|
||||
"@backstage/plugin-catalog": "^0.1.1-alpha.10",
|
||||
"@backstage/theme": "^0.1.1-alpha.10",
|
||||
"@backstage/catalog-model": "^0.1.1-alpha.12",
|
||||
"@backstage/core": "^0.1.1-alpha.12",
|
||||
"@backstage/plugin-catalog": "^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",
|
||||
@@ -36,8 +36,8 @@
|
||||
"react-use": "^14.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.10",
|
||||
"@backstage/dev-utils": "^0.1.1-alpha.10",
|
||||
"@backstage/cli": "^0.1.1-alpha.12",
|
||||
"@backstage/dev-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",
|
||||
@@ -47,6 +47,6 @@
|
||||
"jest-fetch-mock": "^3.0.3"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*.{js,d.ts}"
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-scaffolder-backend",
|
||||
"version": "0.1.1-alpha.10",
|
||||
"version": "0.1.1-alpha.12",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -21,9 +21,9 @@
|
||||
"mock-data": "./scripts/mock-data"
|
||||
},
|
||||
"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/backend-common": "^0.1.1-alpha.12",
|
||||
"@backstage/catalog-model": "^0.1.1-alpha.12",
|
||||
"@backstage/config": "^0.1.1-alpha.12",
|
||||
"@types/express": "^4.17.6",
|
||||
"compression": "^1.7.4",
|
||||
"cors": "^2.8.5",
|
||||
@@ -39,7 +39,7 @@
|
||||
"winston": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.10",
|
||||
"@backstage/cli": "^0.1.1-alpha.12",
|
||||
"@types/dockerode": "^2.5.32",
|
||||
"@types/fs-extra": "^9.0.1",
|
||||
"@types/git-url-parse": "^9.0.0",
|
||||
@@ -49,6 +49,6 @@
|
||||
"yaml": "^1.10.0"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*.{js,d.ts}"
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-scaffolder",
|
||||
"version": "0.1.1-alpha.10",
|
||||
"version": "0.1.1-alpha.12",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -21,8 +21,8 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core": "^0.1.1-alpha.10",
|
||||
"@backstage/theme": "^0.1.1-alpha.10",
|
||||
"@backstage/core": "^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",
|
||||
@@ -32,8 +32,8 @@
|
||||
"react-use": "^14.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.10",
|
||||
"@backstage/dev-utils": "^0.1.1-alpha.10",
|
||||
"@backstage/cli": "^0.1.1-alpha.12",
|
||||
"@backstage/dev-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",
|
||||
@@ -43,6 +43,6 @@
|
||||
"jest-fetch-mock": "^3.0.3"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*.{js,d.ts}"
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-sentry-backend",
|
||||
"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,7 +20,7 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.1.1-alpha.10",
|
||||
"@backstage/backend-common": "^0.1.1-alpha.12",
|
||||
"@types/express": "^4.17.6",
|
||||
"axios": "^0.19.2",
|
||||
"compression": "^1.7.4",
|
||||
@@ -34,10 +34,10 @@
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.10",
|
||||
"@backstage/cli": "^0.1.1-alpha.12",
|
||||
"jest-fetch-mock": "^3.0.3"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*.{js,d.ts}"
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-sentry",
|
||||
"version": "0.1.1-alpha.10",
|
||||
"version": "0.1.1-alpha.12",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -21,8 +21,8 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core": "^0.1.1-alpha.10",
|
||||
"@backstage/theme": "^0.1.1-alpha.10",
|
||||
"@backstage/core": "^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",
|
||||
@@ -34,8 +34,8 @@
|
||||
"timeago.js": "^4.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.10",
|
||||
"@backstage/dev-utils": "^0.1.1-alpha.10",
|
||||
"@backstage/cli": "^0.1.1-alpha.12",
|
||||
"@backstage/dev-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",
|
||||
@@ -45,6 +45,6 @@
|
||||
"jest-fetch-mock": "^3.0.3"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*.{js,d.ts}"
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-tech-radar",
|
||||
"version": "0.1.1-alpha.10",
|
||||
"version": "0.1.1-alpha.12",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -21,9 +21,9 @@
|
||||
"start": "backstage-cli plugin:serve"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core": "^0.1.1-alpha.10",
|
||||
"@backstage/test-utils-core": "^0.1.1-alpha.10",
|
||||
"@backstage/theme": "^0.1.1-alpha.10",
|
||||
"@backstage/core": "^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",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
@@ -36,8 +36,8 @@
|
||||
"react-use": "^14.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.10",
|
||||
"@backstage/dev-utils": "^0.1.1-alpha.10",
|
||||
"@backstage/cli": "^0.1.1-alpha.12",
|
||||
"@backstage/dev-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",
|
||||
@@ -49,6 +49,6 @@
|
||||
"jest-fetch-mock": "^3.0.3"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*.{js,d.ts}"
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-techdocs",
|
||||
"version": "0.1.1-alpha.10",
|
||||
"version": "0.1.1-alpha.12",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -21,8 +21,8 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core": "^0.1.1-alpha.10",
|
||||
"@backstage/theme": "^0.1.1-alpha.10",
|
||||
"@backstage/core": "^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",
|
||||
@@ -31,8 +31,8 @@
|
||||
"react-use": "^14.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.10",
|
||||
"@backstage/dev-utils": "^0.1.1-alpha.10",
|
||||
"@backstage/cli": "^0.1.1-alpha.12",
|
||||
"@backstage/dev-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",
|
||||
@@ -42,6 +42,6 @@
|
||||
"jest-fetch-mock": "^3.0.3"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*.{js,d.ts}"
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-welcome",
|
||||
"version": "0.1.1-alpha.10",
|
||||
"version": "0.1.1-alpha.12",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"private": false,
|
||||
@@ -21,8 +21,8 @@
|
||||
"start": "backstage-cli plugin:serve"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core": "^0.1.1-alpha.10",
|
||||
"@backstage/theme": "^0.1.1-alpha.10",
|
||||
"@backstage/core": "^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",
|
||||
@@ -32,8 +32,8 @@
|
||||
"react-use": "^14.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.10",
|
||||
"@backstage/dev-utils": "^0.1.1-alpha.10",
|
||||
"@backstage/cli": "^0.1.1-alpha.12",
|
||||
"@backstage/dev-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",
|
||||
@@ -43,6 +43,6 @@
|
||||
"jest-fetch-mock": "^3.0.3"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*.{js,d.ts}"
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -884,14 +884,7 @@
|
||||
core-js-pure "^3.0.0"
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2", "@babel/runtime@^7.9.6":
|
||||
version "7.9.6"
|
||||
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.6.tgz#a9102eb5cadedf3f31d08a9ecf294af7827ea29f"
|
||||
integrity sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.5.4":
|
||||
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2", "@babel/runtime@^7.9.6":
|
||||
version "7.10.2"
|
||||
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.2.tgz#d103f21f2602497d38348a32e008637d506db839"
|
||||
integrity sha512-6sF3uQw2ivImfVIl62RZ7MXhO2tap69WeWK57vAaimT6AZbE4FbqjdEJIN1UqoD6wI6B+1n9UiagafH1sxjOtg==
|
||||
@@ -2426,10 +2419,10 @@
|
||||
prop-types "^15.6.1"
|
||||
react-lifecycles-compat "^3.0.4"
|
||||
|
||||
"@rollup/plugin-commonjs@^12.0.0":
|
||||
version "12.0.0"
|
||||
resolved "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-12.0.0.tgz#e2f308ae6057499e0f413f878fff7c3a0fdc02a1"
|
||||
integrity sha512-8+mDQt1QUmN+4Y9D3yCG8AJNewuTSLYPJVzKKUZ+lGeQrI+bV12Tc5HCyt2WdlnG6ihIL/DPbKRJlB40DX40mw==
|
||||
"@rollup/plugin-commonjs@^13.0.0":
|
||||
version "13.0.0"
|
||||
resolved "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-13.0.0.tgz#8a1d684ba6848afe8b9e3d85649d4b2f6f7217ec"
|
||||
integrity sha512-Anxc3qgkAi7peAyesTqGYidG5GRim9jtg8xhmykNaZkImtvjA7Wsqep08D2mYsqw1IF7rA3lYfciLgzUSgRoqw==
|
||||
dependencies:
|
||||
"@rollup/pluginutils" "^3.0.8"
|
||||
commondir "^1.0.1"
|
||||
@@ -2446,14 +2439,16 @@
|
||||
dependencies:
|
||||
"@rollup/pluginutils" "^3.0.8"
|
||||
|
||||
"@rollup/plugin-node-resolve@^7.1.1":
|
||||
version "7.1.3"
|
||||
resolved "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz#80de384edfbd7bfc9101164910f86078151a3eca"
|
||||
integrity sha512-RxtSL3XmdTAE2byxekYLnx+98kEUOrPHF/KRVjLH+DEIHy6kjIw7YINQzn+NXiH/NTrQLAwYs0GWB+csWygA9Q==
|
||||
"@rollup/plugin-node-resolve@^8.1.0":
|
||||
version "8.1.0"
|
||||
resolved "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-8.1.0.tgz#1da5f3d0ccabc8f66f5e3c74462aad76cfd96c47"
|
||||
integrity sha512-ovq7ZM3JJYUUmEjjO+H8tnUdmQmdQudJB7xruX8LFZ1W2q8jXdPUS6SsIYip8ByOApu4RR7729Am9WhCeCMiHA==
|
||||
dependencies:
|
||||
"@rollup/pluginutils" "^3.0.8"
|
||||
"@types/resolve" "0.0.8"
|
||||
builtin-modules "^3.1.0"
|
||||
deep-freeze "^0.0.1"
|
||||
deepmerge "^4.2.2"
|
||||
is-module "^1.0.0"
|
||||
resolve "^1.14.2"
|
||||
|
||||
@@ -2570,15 +2565,15 @@
|
||||
uuid "^3.3.2"
|
||||
|
||||
"@storybook/addon-links@^5.3.17":
|
||||
version "5.3.18"
|
||||
resolved "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-5.3.18.tgz#6b4aec83e158c000221bfe1f43e4a473f0727193"
|
||||
integrity sha512-rDpsAQUr60BEM83TBh7mwKjaZvqTR+CAAMLAxXN+J8DMoOjymjd2sjq4UVJsOQVTnw3wjIc4+xpAL/eYqkJAIQ==
|
||||
version "5.3.19"
|
||||
resolved "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-5.3.19.tgz#3c23e886d44b56978ae254fed3bf8be54c877178"
|
||||
integrity sha512-gn9u8lebREfRsyzxoDPG0O+kOf5aJ0BhzcCJGZZdqha0F6OWHhh8vJYZZvjJ/Qwze+Qt2zjrgWm+Q6+JLD8ugQ==
|
||||
dependencies:
|
||||
"@storybook/addons" "5.3.18"
|
||||
"@storybook/client-logger" "5.3.18"
|
||||
"@storybook/core-events" "5.3.18"
|
||||
"@storybook/addons" "5.3.19"
|
||||
"@storybook/client-logger" "5.3.19"
|
||||
"@storybook/core-events" "5.3.19"
|
||||
"@storybook/csf" "0.0.1"
|
||||
"@storybook/router" "5.3.18"
|
||||
"@storybook/router" "5.3.19"
|
||||
core-js "^3.0.1"
|
||||
global "^4.3.2"
|
||||
prop-types "^15.7.2"
|
||||
@@ -2604,19 +2599,6 @@
|
||||
regenerator-runtime "^0.13.3"
|
||||
util-deprecate "^1.0.2"
|
||||
|
||||
"@storybook/addons@5.3.18":
|
||||
version "5.3.18"
|
||||
resolved "https://registry.npmjs.org/@storybook/addons/-/addons-5.3.18.tgz#5cbba6407ef7a802041c5ee831473bc3bed61f64"
|
||||
integrity sha512-ZQjDgTUDFRLvAiBg2d8FgPgghfQ+9uFyXQbtiGlTBLinrPCeQd7J86qiUES0fcGoohCCw0wWKtvB0WF2z1XNDg==
|
||||
dependencies:
|
||||
"@storybook/api" "5.3.18"
|
||||
"@storybook/channels" "5.3.18"
|
||||
"@storybook/client-logger" "5.3.18"
|
||||
"@storybook/core-events" "5.3.18"
|
||||
core-js "^3.0.1"
|
||||
global "^4.3.2"
|
||||
util-deprecate "^1.0.2"
|
||||
|
||||
"@storybook/addons@5.3.19", "@storybook/addons@^5.3.17":
|
||||
version "5.3.19"
|
||||
resolved "https://registry.npmjs.org/@storybook/addons/-/addons-5.3.19.tgz#3a7010697afd6df9a41b8c8a7351d9a06ff490a4"
|
||||
@@ -2630,32 +2612,6 @@
|
||||
global "^4.3.2"
|
||||
util-deprecate "^1.0.2"
|
||||
|
||||
"@storybook/api@5.3.18":
|
||||
version "5.3.18"
|
||||
resolved "https://registry.npmjs.org/@storybook/api/-/api-5.3.18.tgz#95582ab90d947065e0e34ed603650a3630dcbd16"
|
||||
integrity sha512-QXaccNCARHzPWOuxYndiebGWBZmwiUvRgB9ji0XTJBS3y8K0ZPb5QyuqiKPaEWUj8dBA8rzdDtkW3Yt95Namaw==
|
||||
dependencies:
|
||||
"@reach/router" "^1.2.1"
|
||||
"@storybook/channels" "5.3.18"
|
||||
"@storybook/client-logger" "5.3.18"
|
||||
"@storybook/core-events" "5.3.18"
|
||||
"@storybook/csf" "0.0.1"
|
||||
"@storybook/router" "5.3.18"
|
||||
"@storybook/theming" "5.3.18"
|
||||
"@types/reach__router" "^1.2.3"
|
||||
core-js "^3.0.1"
|
||||
fast-deep-equal "^2.0.1"
|
||||
global "^4.3.2"
|
||||
lodash "^4.17.15"
|
||||
memoizerific "^1.11.3"
|
||||
prop-types "^15.6.2"
|
||||
react "^16.8.3"
|
||||
semver "^6.0.0"
|
||||
shallow-equal "^1.1.0"
|
||||
store2 "^2.7.1"
|
||||
telejson "^3.2.0"
|
||||
util-deprecate "^1.0.2"
|
||||
|
||||
"@storybook/api@5.3.19":
|
||||
version "5.3.19"
|
||||
resolved "https://registry.npmjs.org/@storybook/api/-/api-5.3.19.tgz#77f15e9e2eee59fe1ddeaba1ef39bc34713a6297"
|
||||
@@ -2693,13 +2649,6 @@
|
||||
global "^4.3.2"
|
||||
telejson "^3.2.0"
|
||||
|
||||
"@storybook/channels@5.3.18":
|
||||
version "5.3.18"
|
||||
resolved "https://registry.npmjs.org/@storybook/channels/-/channels-5.3.18.tgz#490c9eaa8292b0571c0f665052b12addf7c35f21"
|
||||
integrity sha512-scP/6td/BJSEOgfN+qaYGDf3E793xye7tIw6W+sYqwg+xdMFO39wVXgVZNpQL6sLEwpJZTaPywCjC6p6ksErqQ==
|
||||
dependencies:
|
||||
core-js "^3.0.1"
|
||||
|
||||
"@storybook/channels@5.3.19":
|
||||
version "5.3.19"
|
||||
resolved "https://registry.npmjs.org/@storybook/channels/-/channels-5.3.19.tgz#65ad7cd19d70aa5eabbb2e5e39ceef5e510bcb7f"
|
||||
@@ -2730,13 +2679,6 @@
|
||||
ts-dedent "^1.1.0"
|
||||
util-deprecate "^1.0.2"
|
||||
|
||||
"@storybook/client-logger@5.3.18":
|
||||
version "5.3.18"
|
||||
resolved "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-5.3.18.tgz#27c9d09d788965db0164be6e168bc3f03adbf88f"
|
||||
integrity sha512-RZjxw4uqZX3Yk27IirbB/pQG+wRsQSSRlKqYa8KQ5bSanm4IrcV9VA1OQbuySW9njE+CexAnakQJ/fENdmurNg==
|
||||
dependencies:
|
||||
core-js "^3.0.1"
|
||||
|
||||
"@storybook/client-logger@5.3.19":
|
||||
version "5.3.19"
|
||||
resolved "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-5.3.19.tgz#fbbd186e82102eaca1d6a5cca640271cae862921"
|
||||
@@ -2771,13 +2713,6 @@
|
||||
simplebar-react "^1.0.0-alpha.6"
|
||||
ts-dedent "^1.1.0"
|
||||
|
||||
"@storybook/core-events@5.3.18":
|
||||
version "5.3.18"
|
||||
resolved "https://registry.npmjs.org/@storybook/core-events/-/core-events-5.3.18.tgz#e5d335f8a2c7dd46502b8f505006f1e111b46d49"
|
||||
integrity sha512-uQ6NYJ5WODXK8DJ7m8y3yUAtWB3n+6XtYztjY+tdkCsLYvTYDXNS+epV+f5Hu9+gB+/Dm+b5Su4jDD+LZB2QWA==
|
||||
dependencies:
|
||||
core-js "^3.0.1"
|
||||
|
||||
"@storybook/core-events@5.3.19":
|
||||
version "5.3.19"
|
||||
resolved "https://registry.npmjs.org/@storybook/core-events/-/core-events-5.3.19.tgz#18020cd52e0d8ef0973a8e9622a10d5f99796f79"
|
||||
@@ -2910,21 +2845,6 @@
|
||||
ts-dedent "^1.1.0"
|
||||
webpack "^4.33.0"
|
||||
|
||||
"@storybook/router@5.3.18":
|
||||
version "5.3.18"
|
||||
resolved "https://registry.npmjs.org/@storybook/router/-/router-5.3.18.tgz#8ab22f1f2f7f957e78baf992030707a62289076e"
|
||||
integrity sha512-6B2U2C75KTSVaCuYYgcubeJGcCSnwsXuEf50hEd5mGqWgHZfojCtGvB7Ko4X+0h8rEC+eNA4p7YBOhlUv9WNrQ==
|
||||
dependencies:
|
||||
"@reach/router" "^1.2.1"
|
||||
"@storybook/csf" "0.0.1"
|
||||
"@types/reach__router" "^1.2.3"
|
||||
core-js "^3.0.1"
|
||||
global "^4.3.2"
|
||||
lodash "^4.17.15"
|
||||
memoizerific "^1.11.3"
|
||||
qs "^6.6.0"
|
||||
util-deprecate "^1.0.2"
|
||||
|
||||
"@storybook/router@5.3.19":
|
||||
version "5.3.19"
|
||||
resolved "https://registry.npmjs.org/@storybook/router/-/router-5.3.19.tgz#0f783b85658f99e4007f74347ad7ef17dbf7fc3a"
|
||||
@@ -2956,24 +2876,6 @@
|
||||
prop-types "^15.7.2"
|
||||
regenerator-runtime "^0.13.3"
|
||||
|
||||
"@storybook/theming@5.3.18":
|
||||
version "5.3.18"
|
||||
resolved "https://registry.npmjs.org/@storybook/theming/-/theming-5.3.18.tgz#35e78de79d9cf8f1248af0dd1c7fa60555761312"
|
||||
integrity sha512-lfFTeLoYwLMKg96N3gn0umghMdAHgJBGuk2OM8Ll84yWtdl9RGnzfiI1Fl7Cr5k95dCF7drLJlJCao1VxUkFSA==
|
||||
dependencies:
|
||||
"@emotion/core" "^10.0.20"
|
||||
"@emotion/styled" "^10.0.17"
|
||||
"@storybook/client-logger" "5.3.18"
|
||||
core-js "^3.0.1"
|
||||
deep-object-diff "^1.1.0"
|
||||
emotion-theming "^10.0.19"
|
||||
global "^4.3.2"
|
||||
memoizerific "^1.11.3"
|
||||
polished "^3.3.1"
|
||||
prop-types "^15.7.2"
|
||||
resolve-from "^5.0.0"
|
||||
ts-dedent "^1.1.0"
|
||||
|
||||
"@storybook/theming@5.3.19":
|
||||
version "5.3.19"
|
||||
resolved "https://registry.npmjs.org/@storybook/theming/-/theming-5.3.19.tgz#177d9819bd64f7a1a6ea2f1920ffa5baf9a5f467"
|
||||
@@ -7434,6 +7336,11 @@ deep-extend@^0.6.0:
|
||||
resolved "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
|
||||
integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
|
||||
|
||||
deep-freeze@^0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.npmjs.org/deep-freeze/-/deep-freeze-0.0.1.tgz#3a0b0005de18672819dfd38cd31f91179c893e84"
|
||||
integrity sha1-OgsABd4YZygZ39OM0x+RF5yJPoQ=
|
||||
|
||||
deep-is@^0.1.3, deep-is@~0.1.3:
|
||||
version "0.1.3"
|
||||
resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
|
||||
@@ -19802,10 +19709,10 @@ yn@^4.0.0:
|
||||
resolved "https://registry.npmjs.org/yn/-/yn-4.0.0.tgz#611480051ea43b510da1dfdbe177ed159f00a979"
|
||||
integrity sha512-huWiiCS4TxKc4SfgmTwW1K7JmXPPAmuXWYy4j9qjQo4+27Kni8mGhAAi1cloRWmBe2EqcLgt3IGqQoRL/MtPgg==
|
||||
|
||||
yup@^0.28.5:
|
||||
version "0.28.5"
|
||||
resolved "https://registry.npmjs.org/yup/-/yup-0.28.5.tgz#85cabb4000d3623ef69be81551190692e631a8a5"
|
||||
integrity sha512-7JZcvpUGUxMKoaEtcoMEM8lCWRaueGNH/A3EhL/UWqfbFm3uloiI+x59Yq4nzhbbYWUTwAsCteaZOJ+VbqI1uw==
|
||||
yup@^0.29.1:
|
||||
version "0.29.1"
|
||||
resolved "https://registry.npmjs.org/yup/-/yup-0.29.1.tgz#35d25aab470a0c3950f66040ba0ff4b1b6efe0d9"
|
||||
integrity sha512-U7mPIbgfQWI6M3hZCJdGFrr+U0laG28FxMAKIgNvgl7OtyYuUoc4uy9qCWYHZjh49b8T7Ug8NNDdiMIEytcXrQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.9.6"
|
||||
fn-name "~3.0.0"
|
||||
|
||||
Reference in New Issue
Block a user