app,core-api: install DiscoveryApi in default app and use it for all auth providers

This commit is contained in:
Patrik Oldsberg
2020-08-20 17:56:33 +02:00
parent 96f1f522b9
commit ea1a36433d
11 changed files with 120 additions and 114 deletions
+16 -13
View File
@@ -24,6 +24,8 @@ import {
ErrorAlerter,
featureFlagsApiRef,
FeatureFlags,
discoveryApiRef,
UrlPatternDiscovery,
GoogleAuth,
GithubAuth,
OAuth2,
@@ -74,7 +76,10 @@ import {
TravisCIApi,
travisCIApiRef,
} from '@roadiehq/backstage-plugin-travis-ci';
import { GithubPullRequestsClient, githubPullRequestsApiRef } from '@roadiehq/backstage-plugin-github-pull-requests';
import {
GithubPullRequestsClient,
githubPullRequestsApiRef,
} from '@roadiehq/backstage-plugin-github-pull-requests';
export const apis = (config: ConfigApi) => {
// eslint-disable-next-line no-console
@@ -85,6 +90,10 @@ export const apis = (config: ConfigApi) => {
const builder = ApiRegistry.builder();
const discoveryApi = builder.add(
discoveryApiRef,
UrlPatternDiscovery.compile(`${backendUrl}/{{ pluginId }}`),
);
const alertApi = builder.add(alertApiRef, new AlertApiForwarder());
const errorApi = builder.add(
errorApiRef,
@@ -116,8 +125,7 @@ export const apis = (config: ConfigApi) => {
builder.add(
googleAuthApiRef,
GoogleAuth.create({
backendUrl,
basePath: '/auth/',
discoveryApi,
oauthRequestApi,
}),
);
@@ -125,8 +133,7 @@ export const apis = (config: ConfigApi) => {
const githubAuthApi = builder.add(
githubAuthApiRef,
GithubAuth.create({
backendUrl,
basePath: '/auth/',
discoveryApi,
oauthRequestApi,
}),
);
@@ -134,8 +141,7 @@ export const apis = (config: ConfigApi) => {
builder.add(
oktaAuthApiRef,
OktaAuth.create({
backendUrl,
basePath: '/auth/',
discoveryApi,
oauthRequestApi,
}),
);
@@ -143,8 +149,7 @@ export const apis = (config: ConfigApi) => {
builder.add(
gitlabAuthApiRef,
GitlabAuth.create({
backendUrl,
basePath: '/auth/',
discoveryApi,
oauthRequestApi,
}),
);
@@ -152,8 +157,7 @@ export const apis = (config: ConfigApi) => {
builder.add(
auth0AuthApiRef,
Auth0Auth.create({
backendUrl,
basePath: '/auth/',
discoveryApi,
oauthRequestApi,
}),
);
@@ -161,8 +165,7 @@ export const apis = (config: ConfigApi) => {
builder.add(
oauth2ApiRef,
OAuth2.create({
backendUrl,
basePath: '/auth/',
discoveryApi,
oauthRequestApi,
}),
);
@@ -27,16 +27,17 @@ import {
AuthRequestOptions,
BackstageIdentity,
} from '../../../definitions/auth';
import { OAuthRequestApi, AuthProvider } from '../../../definitions';
import {
OAuthRequestApi,
AuthProvider,
DiscoveryApi,
} from '../../../definitions';
import { SessionManager } from '../../../../lib/AuthSessionManager/types';
import { RefreshingAuthSessionManager } from '../../../../lib/AuthSessionManager';
import { Observable } from '../../../../types';
type CreateOptions = {
// TODO(Following the words of Rugvip): These two should be grabbed from global config when available, they're not unique to Auth0Auth
backendUrl: string;
basePath: string;
discoveryApi: DiscoveryApi;
oauthRequestApi: OAuthRequestApi;
environment?: string;
@@ -67,15 +68,13 @@ class Auth0Auth
BackstageIdentityApi,
SessionStateApi {
static create({
backendUrl,
basePath,
discoveryApi,
environment = 'development',
provider = DEFAULT_PROVIDER,
oauthRequestApi,
}: CreateOptions) {
const connector = new DefaultAuthConnector({
backendUrl,
basePath,
discoveryApi,
environment,
provider,
oauthRequestApi: oauthRequestApi,
@@ -25,7 +25,11 @@ import {
BackstageIdentity,
AuthRequestOptions,
} from '../../../definitions/auth';
import { OAuthRequestApi, AuthProvider } from '../../../definitions';
import {
OAuthRequestApi,
AuthProvider,
DiscoveryApi,
} from '../../../definitions';
import { SessionManager } from '../../../../lib/AuthSessionManager/types';
import {
AuthSessionStore,
@@ -34,10 +38,7 @@ import {
import { Observable } from '../../../../types';
type CreateOptions = {
// TODO(Rugvip): These two should be grabbed from global config when available, they're not unique to GithubAuth
backendUrl: string;
basePath: string;
discoveryApi: DiscoveryApi;
oauthRequestApi: OAuthRequestApi;
environment?: string;
@@ -62,15 +63,13 @@ const DEFAULT_PROVIDER = {
class GithubAuth implements OAuthApi, SessionStateApi {
static create({
backendUrl,
basePath,
discoveryApi,
environment = 'development',
provider = DEFAULT_PROVIDER,
oauthRequestApi,
}: CreateOptions) {
const connector = new DefaultAuthConnector({
backendUrl,
basePath,
discoveryApi,
environment,
provider,
oauthRequestApi: oauthRequestApi,
@@ -25,15 +25,17 @@ import {
BackstageIdentity,
AuthRequestOptions,
} from '../../../definitions/auth';
import { OAuthRequestApi, AuthProvider } from '../../../definitions';
import {
OAuthRequestApi,
AuthProvider,
DiscoveryApi,
} from '../../../definitions';
import { SessionManager } from '../../../../lib/AuthSessionManager/types';
import { StaticAuthSessionManager } from '../../../../lib/AuthSessionManager';
import { Observable } from '../../../../types';
type CreateOptions = {
backendUrl: string;
basePath: string;
discoveryApi: DiscoveryApi;
oauthRequestApi: OAuthRequestApi;
environment?: string;
@@ -58,15 +60,13 @@ const DEFAULT_PROVIDER = {
class GitlabAuth implements OAuthApi, SessionStateApi {
static create({
backendUrl,
basePath,
discoveryApi,
environment = 'development',
provider = DEFAULT_PROVIDER,
oauthRequestApi,
}: CreateOptions) {
const connector = new DefaultAuthConnector({
backendUrl,
basePath,
discoveryApi,
environment,
provider,
oauthRequestApi,
@@ -28,16 +28,17 @@ import {
AuthRequestOptions,
BackstageIdentity,
} from '../../../definitions/auth';
import { OAuthRequestApi, AuthProvider } from '../../../definitions';
import {
OAuthRequestApi,
AuthProvider,
DiscoveryApi,
} from '../../../definitions';
import { SessionManager } from '../../../../lib/AuthSessionManager/types';
import { RefreshingAuthSessionManager } from '../../../../lib/AuthSessionManager';
import { Observable } from '../../../../types';
type CreateOptions = {
// TODO(Rugvip): These two should be grabbed from global config when available, they're not unique to GoogleAuth
backendUrl: string;
basePath: string;
discoveryApi: DiscoveryApi;
oauthRequestApi: OAuthRequestApi;
environment?: string;
@@ -71,15 +72,13 @@ class GoogleAuth
BackstageIdentityApi,
SessionStateApi {
static create({
backendUrl,
basePath,
discoveryApi,
environment = 'development',
provider = DEFAULT_PROVIDER,
oauthRequestApi,
}: CreateOptions) {
const connector = new DefaultAuthConnector({
backendUrl,
basePath,
discoveryApi,
environment,
provider,
oauthRequestApi: oauthRequestApi,
@@ -19,7 +19,11 @@ import { DefaultAuthConnector } from '../../../../lib/AuthConnector';
import { RefreshingAuthSessionManager } from '../../../../lib/AuthSessionManager';
import { SessionManager } from '../../../../lib/AuthSessionManager/types';
import { Observable } from '../../../../types';
import { AuthProvider, OAuthRequestApi } from '../../../definitions';
import {
AuthProvider,
OAuthRequestApi,
DiscoveryApi,
} from '../../../definitions';
import {
AuthRequestOptions,
BackstageIdentity,
@@ -33,9 +37,7 @@ import {
import { OAuth2Session } from './types';
type CreateOptions = {
backendUrl: string;
basePath: string;
discoveryApi: DiscoveryApi;
oauthRequestApi: OAuthRequestApi;
environment?: string;
@@ -64,15 +66,13 @@ const SCOPE_PREFIX = '';
class OAuth2
implements OAuthApi, OpenIdConnectApi, ProfileInfoApi, SessionStateApi {
static create({
backendUrl,
basePath,
discoveryApi,
environment = 'development',
provider = DEFAULT_PROVIDER,
oauthRequestApi,
}: CreateOptions) {
const connector = new DefaultAuthConnector({
backendUrl,
basePath,
discoveryApi,
environment,
provider,
oauthRequestApi: oauthRequestApi,
@@ -28,15 +28,17 @@ import {
AuthRequestOptions,
BackstageIdentity,
} from '../../../definitions/auth';
import { OAuthRequestApi, AuthProvider } from '../../../definitions';
import {
OAuthRequestApi,
AuthProvider,
DiscoveryApi,
} from '../../../definitions';
import { SessionManager } from '../../../../lib/AuthSessionManager/types';
import { RefreshingAuthSessionManager } from '../../../../lib/AuthSessionManager';
import { Observable } from '../../../../types';
type CreateOptions = {
backendUrl: string;
basePath: string;
discoveryApi: DiscoveryApi;
oauthRequestApi: OAuthRequestApi;
environment?: string;
@@ -80,15 +82,13 @@ class OktaAuth
BackstageIdentityApi,
SessionStateApi {
static create({
backendUrl,
basePath,
discoveryApi,
environment = 'development',
provider = DEFAULT_PROVIDER,
oauthRequestApi,
}: CreateOptions) {
const connector = new DefaultAuthConnector({
backendUrl,
basePath,
discoveryApi,
environment,
provider,
oauthRequestApi: oauthRequestApi,
@@ -18,11 +18,12 @@ import ProviderIcon from '@material-ui/icons/AcUnit';
import { DefaultAuthConnector } from './DefaultAuthConnector';
import MockOAuthApi from '../../apis/implementations/OAuthRequestApi/MockOAuthApi';
import * as loginPopup from '../loginPopup';
import { UrlPatternDiscovery } from '../../apis';
const anyFetch = fetch as any;
const defaultOptions = {
backendUrl: 'http://my-origin',
discoveryApi: UrlPatternDiscovery.compile('http://my-host/api/{{pluginId}}'),
environment: 'production',
provider: {
id: 'my-provider',
@@ -115,7 +116,7 @@ describe('DefaultAuthConnector', () => {
expect(popupSpy).toBeCalledTimes(1);
expect(popupSpy.mock.calls[0][0]).toMatchObject({
url:
'http://my-origin/api/auth/my-provider/start?scope=a%20b&env=production',
'http://my-host/api/auth/my-provider/start?scope=a%20b&env=production',
});
await expect(sessionPromise).resolves.toEqual({
@@ -141,9 +142,9 @@ describe('DefaultAuthConnector', () => {
instantPopup: true,
});
expect(popupSpy).toBeCalledTimes(1);
await expect(sessionPromise).resolves.toBe('my-session');
expect(popupSpy).toBeCalledTimes(1);
});
it('should use join func to join scopes', async () => {
@@ -164,7 +165,7 @@ describe('DefaultAuthConnector', () => {
expect(popupSpy).toBeCalledTimes(1);
expect(popupSpy.mock.calls[0][0]).toMatchObject({
url:
'http://my-origin/api/auth/my-provider/start?scope=-ab-&env=production',
'http://my-host/api/auth/my-provider/start?scope=-ab-&env=production',
});
});
});
@@ -15,21 +15,19 @@
*/
import { AuthRequester } from '../../apis';
import { OAuthRequestApi, AuthProvider } from '../../apis/definitions';
import {
OAuthRequestApi,
AuthProvider,
DiscoveryApi,
} from '../../apis/definitions';
import { showLoginPopup } from '../loginPopup';
import { AuthConnector, CreateSessionOptions } from './types';
const DEFAULT_BASE_PATH = '/api/auth/';
type Options<AuthSession> = {
/**
* The base URL of the auth backend.
* DiscoveryApi instance used to locate the auth backend endpoint.
*/
backendUrl?: string;
/**
* Base path of the auth requests, defaults to /api/auth/
*/
basePath?: string;
discoveryApi: DiscoveryApi;
/**
* Environment hint passed on to auth backend, for example 'production' or 'development'
*/
@@ -64,8 +62,7 @@ function defaultJoinScopes(scopes: Set<string>) {
*/
export class DefaultAuthConnector<AuthSession>
implements AuthConnector<AuthSession> {
private readonly backendUrl: string;
private readonly basePath: string;
private readonly discoveryApi: DiscoveryApi;
private readonly environment: string;
private readonly provider: AuthProvider & { id: string };
private readonly joinScopesFunc: (scopes: Set<string>) => string;
@@ -74,8 +71,7 @@ export class DefaultAuthConnector<AuthSession>
constructor(options: Options<AuthSession>) {
const {
backendUrl = window.location.origin,
basePath = DEFAULT_BASE_PATH,
discoveryApi,
environment,
provider,
joinScopes = defaultJoinScopes,
@@ -88,8 +84,7 @@ export class DefaultAuthConnector<AuthSession>
onAuthRequest: scopes => this.showPopup(scopes),
});
this.backendUrl = backendUrl;
this.basePath = basePath;
this.discoveryApi = discoveryApi;
this.environment = environment;
this.provider = provider;
this.joinScopesFunc = joinScopes;
@@ -104,12 +99,15 @@ export class DefaultAuthConnector<AuthSession>
}
async refreshSession(): Promise<any> {
const res = await fetch(this.buildUrl('/refresh', { optional: true }), {
headers: {
'x-requested-with': 'XMLHttpRequest',
const res = await fetch(
await this.buildUrl('/refresh', { optional: true }),
{
headers: {
'x-requested-with': 'XMLHttpRequest',
},
credentials: 'include',
},
credentials: 'include',
}).catch(error => {
).catch(error => {
throw new Error(`Auth refresh request failed, ${error}`);
});
@@ -134,7 +132,7 @@ export class DefaultAuthConnector<AuthSession>
}
async removeSession(): Promise<void> {
const res = await fetch(this.buildUrl('/logout'), {
const res = await fetch(await this.buildUrl('/logout'), {
method: 'POST',
headers: {
'x-requested-with': 'XMLHttpRequest',
@@ -153,13 +151,12 @@ export class DefaultAuthConnector<AuthSession>
private async showPopup(scopes: Set<string>): Promise<AuthSession> {
const scope = this.joinScopesFunc(scopes);
const popupUrl = this.buildUrl('/start', { scope });
const { origin } = new URL(this.backendUrl);
const popupUrl = await this.buildUrl('/start', { scope });
const payload = await showLoginPopup({
url: popupUrl,
name: `${this.provider.title} Login`,
origin,
origin: new URL(popupUrl).origin,
width: 450,
height: 730,
});
@@ -167,16 +164,17 @@ export class DefaultAuthConnector<AuthSession>
return await this.sessionTransform(payload);
}
private buildUrl(
private async buildUrl(
path: string,
query?: { [key: string]: string | boolean | undefined },
): string {
): Promise<string> {
const baseUrl = await this.discoveryApi.getBaseUrl('auth');
const queryString = this.buildQueryString({
...query,
env: this.environment,
});
return `${this.backendUrl}${this.basePath}${this.provider.id}${path}${queryString}`;
return `${baseUrl}/${this.provider.id}${path}${queryString}`;
}
private buildQueryString(query?: {
+21 -16
View File
@@ -24,6 +24,8 @@ import {
AlertApiForwarder,
oauthRequestApiRef,
OAuthRequestManager,
UrlPatternDiscovery,
discoveryApiRef,
GoogleAuth,
googleAuthApiRef,
GithubAuth,
@@ -58,46 +60,49 @@ export const oauthRequestApiFactory = createApiFactory({
factory: () => new OAuthRequestManager(),
});
export const discoveryApiFactory = createApiFactory({
implements: discoveryApiRef,
deps: {},
factory: () =>
UrlPatternDiscovery.compile(`http://localhost:7000/{{ pluginId }}`),
});
export const googleAuthApiFactory = createApiFactory({
implements: googleAuthApiRef,
deps: { oauthRequestApi: oauthRequestApiRef },
factory: ({ oauthRequestApi }) =>
deps: { discoveryApi: discoveryApiRef, oauthRequestApi: oauthRequestApiRef },
factory: ({ discoveryApi, oauthRequestApi }) =>
GoogleAuth.create({
backendUrl: 'http://localhost:7000',
basePath: '/auth/',
discoveryApi,
oauthRequestApi,
}),
});
export const githubAuthApiFactory = createApiFactory({
implements: githubAuthApiRef,
deps: { oauthRequestApi: oauthRequestApiRef },
factory: ({ oauthRequestApi }) =>
deps: { discoveryApi: discoveryApiRef, oauthRequestApi: oauthRequestApiRef },
factory: ({ discoveryApi, oauthRequestApi }) =>
GithubAuth.create({
backendUrl: 'http://localhost:7000',
basePath: '/auth/',
discoveryApi,
oauthRequestApi,
}),
});
export const gitlabAuthApiFactory = createApiFactory({
implements: gitlabAuthApiRef,
deps: { oauthRequestApi: oauthRequestApiRef },
factory: ({ oauthRequestApi }) =>
deps: { discoveryApi: discoveryApiRef, oauthRequestApi: oauthRequestApiRef },
factory: ({ discoveryApi, oauthRequestApi }) =>
GitlabAuth.create({
backendUrl: 'http://localhost:7000',
basePath: '/auth/',
discoveryApi,
oauthRequestApi,
}),
});
export const auth0AuthApiFactory = createApiFactory({
implements: auth0AuthApiRef,
deps: { oauthRequestApi: oauthRequestApiRef },
factory: ({ oauthRequestApi }) =>
deps: { discoveryApi: discoveryApiRef, oauthRequestApi: oauthRequestApiRef },
factory: ({ discoveryApi, oauthRequestApi }) =>
Auth0Auth.create({
backendUrl: 'http://localhost:7000',
basePath: '/auth/',
discoveryApi,
oauthRequestApi,
}),
});
@@ -26,6 +26,7 @@ import {
githubAuthApiRef,
GithubAuth,
OAuthRequestManager,
UrlPatternDiscovery,
} from '@backstage/core';
import { gitOpsApiRef, GitOpsRestApi } from '../../api';
@@ -37,8 +38,9 @@ describe('ProfileCatalog', () => {
[
githubAuthApiRef,
GithubAuth.create({
backendUrl: 'http://localhost:7000',
basePath: '/auth/',
discoveryApi: UrlPatternDiscovery.compile(
'http://example.com/{{pluginId}}',
),
oauthRequestApi,
}),
],