Move configuration of redirect authentication flow to a global parameter named "enableExperimentalRedirectFlow". Pass the configApi instance into authentication providers to read new configuration parameter.
Signed-off-by: headphonejames <generalfuzz@gmail.com>
This commit is contained in:
@@ -293,7 +293,6 @@ auth:
|
||||
# path: my-sessions
|
||||
|
||||
environment: development
|
||||
authFlow: redirect
|
||||
### Providing an auth.session.secret will enable session support in the auth-backend
|
||||
# session:
|
||||
# secret: custom session secret
|
||||
|
||||
@@ -128,10 +128,10 @@ export const apis = [
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
|
||||
GoogleAuth.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
authFlow: configApi.getOptionalString('auth.authFlow'),
|
||||
}),
|
||||
}),
|
||||
createApiFactory({
|
||||
@@ -143,10 +143,10 @@ export const apis = [
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
|
||||
MicrosoftAuth.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
authFlow: configApi.getOptionalString('auth.authFlow'),
|
||||
}),
|
||||
}),
|
||||
createApiFactory({
|
||||
@@ -158,11 +158,11 @@ export const apis = [
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
|
||||
GithubAuth.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
defaultScopes: ['read:user'],
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
authFlow: configApi.getOptionalString('auth.authFlow'),
|
||||
}),
|
||||
}),
|
||||
createApiFactory({
|
||||
@@ -174,10 +174,10 @@ export const apis = [
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
|
||||
OktaAuth.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
authFlow: configApi.getOptionalString('auth.authFlow'),
|
||||
}),
|
||||
}),
|
||||
createApiFactory({
|
||||
@@ -189,10 +189,10 @@ export const apis = [
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
|
||||
GitlabAuth.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
authFlow: configApi.getOptionalString('auth.authFlow'),
|
||||
}),
|
||||
}),
|
||||
createApiFactory({
|
||||
@@ -204,10 +204,10 @@ export const apis = [
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
|
||||
OneLoginAuth.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
authFlow: configApi.getOptionalString('auth.authFlow'),
|
||||
}),
|
||||
}),
|
||||
createApiFactory({
|
||||
@@ -219,11 +219,11 @@ export const apis = [
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
|
||||
BitbucketAuth.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
defaultScopes: ['team'],
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
authFlow: configApi.getOptionalString('auth.authFlow'),
|
||||
}),
|
||||
}),
|
||||
createApiFactory({
|
||||
@@ -235,10 +235,10 @@ export const apis = [
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) => {
|
||||
return AtlassianAuth.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
authFlow: configApi.getOptionalString('auth.authFlow'),
|
||||
});
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -24,6 +24,7 @@ import { BackstagePlugin } from '@backstage/core-plugin-api';
|
||||
import { bitbucketAuthApiRef } from '@backstage/core-plugin-api';
|
||||
import { ComponentType } from 'react';
|
||||
import { Config } from '@backstage/config';
|
||||
import { ConfigApi } from '@backstage/core-plugin-api';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { DiscoveryApi } from '@backstage/core-plugin-api';
|
||||
import { ErrorApi } from '@backstage/core-plugin-api';
|
||||
@@ -263,7 +264,7 @@ export type AuthApiCreateOptions = {
|
||||
discoveryApi: DiscoveryApi;
|
||||
environment?: string;
|
||||
provider?: AuthProviderInfo;
|
||||
authFlow?: string;
|
||||
configApi: ConfigApi;
|
||||
};
|
||||
|
||||
// @public
|
||||
@@ -492,8 +493,6 @@ export type OAuthApiCreateOptions = AuthApiCreateOptions & {
|
||||
|
||||
// @public
|
||||
export class OAuthRequestManager implements OAuthRequestApi {
|
||||
// (undocumented)
|
||||
authFlow(): string;
|
||||
// (undocumented)
|
||||
authRequest$(): Observable<PendingOAuthRequest[]>;
|
||||
// (undocumented)
|
||||
@@ -516,10 +515,10 @@ export class OneLoginAuth {
|
||||
|
||||
// @public
|
||||
export type OneLoginAuthCreateOptions = {
|
||||
configApi: ConfigApi;
|
||||
discoveryApi: DiscoveryApi;
|
||||
oauthRequestApi: OAuthRequestApi;
|
||||
environment?: string;
|
||||
authFlow?: string;
|
||||
provider?: AuthProviderInfo;
|
||||
};
|
||||
|
||||
|
||||
Vendored
+7
-6
@@ -114,11 +114,12 @@ export interface Config {
|
||||
* @visibility frontend
|
||||
*/
|
||||
environment?: string;
|
||||
/**
|
||||
$ The authentication flow type - currently supports 'popup' and 'redirect'
|
||||
* default value: 'popup'
|
||||
* @visibility frontend
|
||||
*/
|
||||
authFlow?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
$ Enable redirect authentication flow type, instead of a popup for authentication
|
||||
* default value: 'false'
|
||||
* @visibility frontend
|
||||
*/
|
||||
enableExperimentalRedirectFlow?: boolean;
|
||||
}
|
||||
|
||||
@@ -25,14 +25,12 @@ describe('MockOAuthApi', () => {
|
||||
const requester1 = mock.createAuthRequester({
|
||||
provider: { icon: () => null, title: 'Test', id: 'test-provider' },
|
||||
onAuthRequest: authHandler1,
|
||||
authFlow: 'popup',
|
||||
});
|
||||
|
||||
const authHandler2 = jest.fn().mockResolvedValue('other');
|
||||
const requester2 = mock.createAuthRequester({
|
||||
provider: { icon: () => null, title: 'Test', id: 'test-provider' },
|
||||
onAuthRequest: authHandler2,
|
||||
authFlow: 'popup',
|
||||
});
|
||||
|
||||
const promises = [
|
||||
@@ -70,14 +68,12 @@ describe('MockOAuthApi', () => {
|
||||
const requester1 = mock.createAuthRequester({
|
||||
provider: { icon: () => null, title: 'Test', id: 'test-provider' },
|
||||
onAuthRequest: authHandler1,
|
||||
authFlow: 'popup',
|
||||
});
|
||||
|
||||
const authHandler2 = jest.fn();
|
||||
const requester2 = mock.createAuthRequester({
|
||||
provider: { icon: () => null, title: 'Test', id: 'test-provider' },
|
||||
onAuthRequest: authHandler2,
|
||||
authFlow: 'popup',
|
||||
});
|
||||
|
||||
const promises = [
|
||||
|
||||
-1
@@ -30,7 +30,6 @@ describe('OAuthRequestManager', () => {
|
||||
icon: () => null,
|
||||
},
|
||||
onAuthRequest: async () => 'hello',
|
||||
authFlow: 'popup',
|
||||
});
|
||||
|
||||
expect(reqSpy).toHaveBeenCalledTimes(0);
|
||||
|
||||
@@ -37,10 +37,8 @@ export class OAuthRequestManager implements OAuthRequestApi {
|
||||
private readonly subject = new BehaviorSubject<PendingOAuthRequest[]>([]);
|
||||
private currentRequests: PendingOAuthRequest[] = [];
|
||||
private handlerCount = 0;
|
||||
private authFlowStr: string = 'popup';
|
||||
|
||||
createAuthRequester<T>(options: OAuthRequesterOptions<T>): OAuthRequester<T> {
|
||||
this.authFlowStr = options.authFlow;
|
||||
const handler = new OAuthPendingRequests<T>();
|
||||
|
||||
const index = this.handlerCount;
|
||||
@@ -93,8 +91,4 @@ export class OAuthRequestManager implements OAuthRequestApi {
|
||||
authRequest$(): Observable<PendingOAuthRequest[]> {
|
||||
return this.subject;
|
||||
}
|
||||
|
||||
authFlow(): string {
|
||||
return this.authFlowStr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,19 +32,19 @@ const DEFAULT_PROVIDER = {
|
||||
export default class AtlassianAuth {
|
||||
static create(options: OAuthApiCreateOptions): typeof atlassianAuthApiRef.T {
|
||||
const {
|
||||
configApi,
|
||||
discoveryApi,
|
||||
environment = 'development',
|
||||
authFlow = 'popup',
|
||||
provider = DEFAULT_PROVIDER,
|
||||
oauthRequestApi,
|
||||
} = options;
|
||||
|
||||
return OAuth2.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
provider,
|
||||
environment,
|
||||
authFlow,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
import MockOAuthApi from '../../OAuthRequestApi/MockOAuthApi';
|
||||
import { UrlPatternDiscovery } from '../../DiscoveryApi';
|
||||
import BitbucketAuth from './BitbucketAuth';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { ConfigApi } from '@backstage/core-plugin-api';
|
||||
|
||||
const getSession = jest.fn();
|
||||
|
||||
@@ -32,6 +34,10 @@ describe('BitbucketAuth', () => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
const configApi: ConfigApi = new ConfigReader({
|
||||
enableExperimentalRedirectFlow: false,
|
||||
});
|
||||
|
||||
it.each([
|
||||
['team api write_repository', ['team', 'api', 'write_repository']],
|
||||
['read_repository sudo', ['read_repository', 'sudo']],
|
||||
@@ -39,6 +45,7 @@ describe('BitbucketAuth', () => {
|
||||
const gitlabAuth = BitbucketAuth.create({
|
||||
oauthRequestApi: new MockOAuthApi(),
|
||||
discoveryApi: UrlPatternDiscovery.compile('http://example.com'),
|
||||
configApi: configApi,
|
||||
});
|
||||
|
||||
gitlabAuth.getAccessToken(scope);
|
||||
|
||||
@@ -47,20 +47,20 @@ const DEFAULT_PROVIDER = {
|
||||
export default class BitbucketAuth {
|
||||
static create(options: OAuthApiCreateOptions): typeof bitbucketAuthApiRef.T {
|
||||
const {
|
||||
configApi,
|
||||
discoveryApi,
|
||||
environment = 'development',
|
||||
authFlow = 'popup',
|
||||
provider = DEFAULT_PROVIDER,
|
||||
oauthRequestApi,
|
||||
defaultScopes = ['team'],
|
||||
} = options;
|
||||
|
||||
return OAuth2.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
provider,
|
||||
environment,
|
||||
authFlow,
|
||||
defaultScopes,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
import { UrlPatternDiscovery } from '../../DiscoveryApi';
|
||||
import MockOAuthApi from '../../OAuthRequestApi/MockOAuthApi';
|
||||
import GithubAuth from './GithubAuth';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { ConfigApi } from '@backstage/core-plugin-api';
|
||||
|
||||
const getSession = jest.fn();
|
||||
|
||||
@@ -32,8 +34,13 @@ describe('GithubAuth', () => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
const configApi: ConfigApi = new ConfigReader({
|
||||
enableExperimentalRedirectFlow: false,
|
||||
});
|
||||
|
||||
it('should forward access token request to session manager', async () => {
|
||||
const githubAuth = GithubAuth.create({
|
||||
configApi: configApi,
|
||||
oauthRequestApi: new MockOAuthApi(),
|
||||
discoveryApi: UrlPatternDiscovery.compile('http://example.com'),
|
||||
});
|
||||
|
||||
@@ -32,21 +32,21 @@ const DEFAULT_PROVIDER = {
|
||||
export default class GithubAuth {
|
||||
static create(options: OAuthApiCreateOptions): typeof githubAuthApiRef.T {
|
||||
const {
|
||||
configApi,
|
||||
discoveryApi,
|
||||
environment = 'development',
|
||||
provider = DEFAULT_PROVIDER,
|
||||
oauthRequestApi,
|
||||
defaultScopes = ['read:user'],
|
||||
authFlow = 'popup',
|
||||
} = options;
|
||||
|
||||
return OAuth2.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
provider,
|
||||
environment,
|
||||
defaultScopes,
|
||||
authFlow,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
import MockOAuthApi from '../../OAuthRequestApi/MockOAuthApi';
|
||||
import { UrlPatternDiscovery } from '../../DiscoveryApi';
|
||||
import GitlabAuth from './GitlabAuth';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { ConfigApi } from '@backstage/core-plugin-api';
|
||||
|
||||
const getSession = jest.fn();
|
||||
|
||||
@@ -39,7 +41,12 @@ describe('GitlabAuth', () => {
|
||||
],
|
||||
['read_repository sudo', ['read_repository', 'sudo']],
|
||||
])(`should normalize scopes correctly - %p`, (scope, scopes) => {
|
||||
const configApi: ConfigApi = new ConfigReader({
|
||||
enableExperimentalRedirectFlow: false,
|
||||
});
|
||||
|
||||
const gitlabAuth = GitlabAuth.create({
|
||||
configApi: configApi,
|
||||
oauthRequestApi: new MockOAuthApi(),
|
||||
discoveryApi: UrlPatternDiscovery.compile('http://example.com'),
|
||||
});
|
||||
|
||||
@@ -32,20 +32,20 @@ const DEFAULT_PROVIDER = {
|
||||
export default class GitlabAuth {
|
||||
static create(options: OAuthApiCreateOptions): typeof gitlabAuthApiRef.T {
|
||||
const {
|
||||
configApi,
|
||||
discoveryApi,
|
||||
environment = 'development',
|
||||
authFlow = 'popup',
|
||||
provider = DEFAULT_PROVIDER,
|
||||
oauthRequestApi,
|
||||
defaultScopes = ['read_user'],
|
||||
} = options;
|
||||
|
||||
return OAuth2.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
provider,
|
||||
environment,
|
||||
authFlow,
|
||||
defaultScopes,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
import GoogleAuth from './GoogleAuth';
|
||||
import MockOAuthApi from '../../OAuthRequestApi/MockOAuthApi';
|
||||
import { UrlPatternDiscovery } from '../../DiscoveryApi';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { ConfigApi } from '@backstage/core-plugin-api';
|
||||
|
||||
const PREFIX = 'https://www.googleapis.com/auth/';
|
||||
|
||||
@@ -58,7 +60,12 @@ describe('GoogleAuth', () => {
|
||||
[`${PREFIX}profile`, [`${PREFIX}profile`]],
|
||||
[`${PREFIX}openid`, [`${PREFIX}openid`]],
|
||||
])(`should normalize scopes correctly - %p`, (scope, scopes) => {
|
||||
const configApi: ConfigApi = new ConfigReader({
|
||||
enableExperimentalRedirectFlow: false,
|
||||
});
|
||||
|
||||
const googleAuth = GoogleAuth.create({
|
||||
configApi: configApi,
|
||||
oauthRequestApi: new MockOAuthApi(),
|
||||
discoveryApi: UrlPatternDiscovery.compile('http://example.com'),
|
||||
});
|
||||
|
||||
@@ -34,10 +34,10 @@ const SCOPE_PREFIX = 'https://www.googleapis.com/auth/';
|
||||
export default class GoogleAuth {
|
||||
static create(options: OAuthApiCreateOptions): typeof googleAuthApiRef.T {
|
||||
const {
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
environment = 'development',
|
||||
authFlow = 'popup',
|
||||
provider = DEFAULT_PROVIDER,
|
||||
defaultScopes = [
|
||||
'openid',
|
||||
@@ -47,11 +47,11 @@ export default class GoogleAuth {
|
||||
} = options;
|
||||
|
||||
return OAuth2.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
provider,
|
||||
environment,
|
||||
authFlow,
|
||||
defaultScopes,
|
||||
scopeTransform(scopes: string[]) {
|
||||
return scopes.map(scope => {
|
||||
|
||||
@@ -32,8 +32,8 @@ const DEFAULT_PROVIDER = {
|
||||
export default class MicrosoftAuth {
|
||||
static create(options: OAuthApiCreateOptions): typeof microsoftAuthApiRef.T {
|
||||
const {
|
||||
configApi,
|
||||
environment = 'development',
|
||||
authFlow = 'popup',
|
||||
provider = DEFAULT_PROVIDER,
|
||||
oauthRequestApi,
|
||||
discoveryApi,
|
||||
@@ -47,11 +47,11 @@ export default class MicrosoftAuth {
|
||||
} = options;
|
||||
|
||||
return OAuth2.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
provider,
|
||||
environment,
|
||||
authFlow,
|
||||
defaultScopes,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
import OAuth2 from './OAuth2';
|
||||
import MockOAuthApi from '../../OAuthRequestApi/MockOAuthApi';
|
||||
import { UrlPatternDiscovery } from '../../DiscoveryApi';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { ConfigApi } from '@backstage/core-plugin-api';
|
||||
|
||||
const theFuture = new Date(Date.now() + 3600000);
|
||||
const thePast = new Date(Date.now() - 10);
|
||||
@@ -34,12 +36,17 @@ jest.mock('../../../../lib/AuthSessionManager', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const configApi: ConfigApi = new ConfigReader({
|
||||
enableExperimentalRedirectFlow: false,
|
||||
});
|
||||
|
||||
describe('OAuth2', () => {
|
||||
it('should get refreshed access token', async () => {
|
||||
getSession = jest.fn().mockResolvedValue({
|
||||
providerInfo: { accessToken: 'access-token', expiresAt: theFuture },
|
||||
});
|
||||
const oauth2 = OAuth2.create({
|
||||
configApi: configApi,
|
||||
scopeTransform: scopeTransform,
|
||||
oauthRequestApi: new MockOAuthApi(),
|
||||
discoveryApi: UrlPatternDiscovery.compile('http://example.com'),
|
||||
@@ -59,6 +66,7 @@ describe('OAuth2', () => {
|
||||
providerInfo: { accessToken: 'access-token', expiresAt: theFuture },
|
||||
});
|
||||
const oauth2 = OAuth2.create({
|
||||
configApi: configApi,
|
||||
scopeTransform: scopes => scopes.map(scope => `my-prefix/${scope}`),
|
||||
oauthRequestApi: new MockOAuthApi(),
|
||||
discoveryApi: UrlPatternDiscovery.compile('http://example.com'),
|
||||
@@ -75,7 +83,9 @@ describe('OAuth2', () => {
|
||||
getSession = jest.fn().mockResolvedValue({
|
||||
providerInfo: { idToken: 'id-token', expiresAt: theFuture },
|
||||
});
|
||||
|
||||
const oauth2 = OAuth2.create({
|
||||
configApi: configApi,
|
||||
scopeTransform: scopeTransform,
|
||||
oauthRequestApi: new MockOAuthApi(),
|
||||
discoveryApi: UrlPatternDiscovery.compile('http://example.com'),
|
||||
@@ -90,6 +100,7 @@ describe('OAuth2', () => {
|
||||
providerInfo: { idToken: 'id-token', expiresAt: theFuture },
|
||||
});
|
||||
const oauth2 = OAuth2.create({
|
||||
configApi: configApi,
|
||||
scopeTransform: scopes => scopes.map(scope => `my-prefix/${scope}`),
|
||||
oauthRequestApi: new MockOAuthApi(),
|
||||
discoveryApi: UrlPatternDiscovery.compile('http://example.com'),
|
||||
@@ -113,6 +124,7 @@ describe('OAuth2', () => {
|
||||
})
|
||||
.mockRejectedValue(error);
|
||||
const oauth2 = OAuth2.create({
|
||||
configApi: configApi,
|
||||
scopeTransform: scopes => scopes.map(scope => `my-prefix/${scope}`),
|
||||
oauthRequestApi: new MockOAuthApi(),
|
||||
discoveryApi: UrlPatternDiscovery.compile('http://example.com'),
|
||||
@@ -147,6 +159,7 @@ describe('OAuth2', () => {
|
||||
},
|
||||
});
|
||||
const oauth2 = OAuth2.create({
|
||||
configApi: configApi,
|
||||
scopeTransform: scopes => scopes.map(scope => `my-prefix/${scope}`),
|
||||
oauthRequestApi: new MockOAuthApi(),
|
||||
discoveryApi: UrlPatternDiscovery.compile('http://example.com'),
|
||||
|
||||
@@ -72,21 +72,21 @@ export default class OAuth2
|
||||
{
|
||||
static create(options: OAuth2CreateOptions) {
|
||||
const {
|
||||
configApi,
|
||||
discoveryApi,
|
||||
environment = 'development',
|
||||
provider = DEFAULT_PROVIDER,
|
||||
oauthRequestApi,
|
||||
defaultScopes = [],
|
||||
authFlow = 'popup',
|
||||
scopeTransform = x => x,
|
||||
} = options;
|
||||
|
||||
const connector = new DefaultAuthConnector({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
environment,
|
||||
provider,
|
||||
oauthRequestApi: oauthRequestApi,
|
||||
authFlow,
|
||||
sessionTransform(res: OAuth2Response): OAuth2Session {
|
||||
return {
|
||||
...res,
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
import OktaAuth from './OktaAuth';
|
||||
import MockOAuthApi from '../../OAuthRequestApi/MockOAuthApi';
|
||||
import { UrlPatternDiscovery } from '../../DiscoveryApi';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { ConfigApi } from '@backstage/core-plugin-api';
|
||||
|
||||
const PREFIX = 'okta.';
|
||||
|
||||
@@ -50,7 +52,11 @@ describe('OktaAuth', () => {
|
||||
[`${PREFIX}profile`, [`${PREFIX}profile`]],
|
||||
[`${PREFIX}openid`, [`${PREFIX}openid`]],
|
||||
])(`should normalize scopes correctly - %p`, (scope, scopes) => {
|
||||
const configApi: ConfigApi = new ConfigReader({
|
||||
enableExperimentalRedirectFlow: false,
|
||||
});
|
||||
const auth = OktaAuth.create({
|
||||
configApi: configApi,
|
||||
oauthRequestApi: new MockOAuthApi(),
|
||||
discoveryApi: UrlPatternDiscovery.compile('http://example.com'),
|
||||
});
|
||||
|
||||
@@ -44,20 +44,20 @@ const OKTA_SCOPE_PREFIX: string = 'okta.';
|
||||
export default class OktaAuth {
|
||||
static create(options: OAuthApiCreateOptions): typeof oktaAuthApiRef.T {
|
||||
const {
|
||||
configApi,
|
||||
discoveryApi,
|
||||
environment = 'development',
|
||||
authFlow = 'popup',
|
||||
provider = DEFAULT_PROVIDER,
|
||||
oauthRequestApi,
|
||||
defaultScopes = ['openid', 'email', 'profile', 'offline_access'],
|
||||
} = options;
|
||||
|
||||
return OAuth2.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
provider,
|
||||
environment,
|
||||
authFlow,
|
||||
defaultScopes,
|
||||
scopeTransform(scopes) {
|
||||
return scopes.map(scope => {
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
oneloginAuthApiRef,
|
||||
OAuthRequestApi,
|
||||
AuthProviderInfo,
|
||||
ConfigApi,
|
||||
DiscoveryApi,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { OAuth2 } from '../oauth2';
|
||||
@@ -27,10 +28,10 @@ import { OAuth2 } from '../oauth2';
|
||||
* @public
|
||||
*/
|
||||
export type OneLoginAuthCreateOptions = {
|
||||
configApi: ConfigApi;
|
||||
discoveryApi: DiscoveryApi;
|
||||
oauthRequestApi: OAuthRequestApi;
|
||||
environment?: string;
|
||||
authFlow?: string;
|
||||
provider?: AuthProviderInfo;
|
||||
};
|
||||
|
||||
@@ -62,19 +63,19 @@ export default class OneLoginAuth {
|
||||
options: OneLoginAuthCreateOptions,
|
||||
): typeof oneloginAuthApiRef.T {
|
||||
const {
|
||||
configApi,
|
||||
discoveryApi,
|
||||
environment = 'development',
|
||||
authFlow = 'popup',
|
||||
provider = DEFAULT_PROVIDER,
|
||||
oauthRequestApi,
|
||||
} = options;
|
||||
|
||||
return OAuth2.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
provider,
|
||||
environment,
|
||||
authFlow: authFlow,
|
||||
defaultScopes: ['openid', 'email', 'profile', 'offline_access'],
|
||||
scopeTransform(scopes) {
|
||||
return scopes.map(scope => {
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
AuthProviderInfo,
|
||||
ConfigApi,
|
||||
DiscoveryApi,
|
||||
OAuthRequestApi,
|
||||
} from '@backstage/core-plugin-api';
|
||||
@@ -37,5 +37,5 @@ export type AuthApiCreateOptions = {
|
||||
discoveryApi: DiscoveryApi;
|
||||
environment?: string;
|
||||
provider?: AuthProviderInfo;
|
||||
authFlow?: string;
|
||||
configApi: ConfigApi;
|
||||
};
|
||||
|
||||
@@ -21,6 +21,8 @@ import { UrlPatternDiscovery } from '../../apis';
|
||||
import { setupRequestMockHandlers } from '@backstage/test-utils';
|
||||
import { setupServer } from 'msw/node';
|
||||
import { rest } from 'msw';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { ConfigApi } from '@backstage/core-plugin-api';
|
||||
|
||||
jest.mock('../loginPopup', () => {
|
||||
return {
|
||||
@@ -28,6 +30,10 @@ jest.mock('../loginPopup', () => {
|
||||
};
|
||||
});
|
||||
|
||||
const configApi: ConfigApi = new ConfigReader({
|
||||
enableExperimentalRedirectFlow: false,
|
||||
});
|
||||
|
||||
const defaultOptions = {
|
||||
discoveryApi: UrlPatternDiscovery.compile('http://my-host/api/{{pluginId}}'),
|
||||
environment: 'production',
|
||||
@@ -42,7 +48,7 @@ const defaultOptions = {
|
||||
scopes: new Set(res.scopes.split(' ')),
|
||||
expiresAt: new Date(Date.now() + expiresInSeconds * 1000),
|
||||
}),
|
||||
authFlow: 'popup',
|
||||
configApi: configApi,
|
||||
};
|
||||
|
||||
describe('DefaultAuthConnector', () => {
|
||||
|
||||
@@ -13,12 +13,12 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
OAuthRequester,
|
||||
OAuthRequestApi,
|
||||
AuthProviderInfo,
|
||||
ConfigApi,
|
||||
DiscoveryApi,
|
||||
OAuthRequestApi,
|
||||
OAuthRequester,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { showLoginPopup } from '../loginPopup';
|
||||
import { AuthConnector, CreateSessionOptions } from './types';
|
||||
@@ -50,9 +50,9 @@ type Options<AuthSession> = {
|
||||
*/
|
||||
sessionTransform?(response: any): AuthSession | Promise<AuthSession>;
|
||||
/**
|
||||
* The UI authentication flow with backend authentication api. Supports either 'popup' or 'redirect'.
|
||||
* ConfigApi instance used to configure authentication flow of pop-up or redirect.
|
||||
*/
|
||||
authFlow: string;
|
||||
configApi: ConfigApi;
|
||||
};
|
||||
|
||||
function defaultJoinScopes(scopes: Set<string>) {
|
||||
@@ -68,36 +68,37 @@ export class DefaultAuthConnector<AuthSession>
|
||||
implements AuthConnector<AuthSession>
|
||||
{
|
||||
private readonly discoveryApi: DiscoveryApi;
|
||||
private readonly configApi: ConfigApi;
|
||||
private readonly environment: string;
|
||||
private readonly provider: AuthProviderInfo;
|
||||
private readonly joinScopesFunc: (scopes: Set<string>) => string;
|
||||
private readonly authRequester: OAuthRequester<AuthSession>;
|
||||
private readonly sessionTransform: (response: any) => Promise<AuthSession>;
|
||||
private readonly authFlow: string;
|
||||
|
||||
constructor(options: Options<AuthSession>) {
|
||||
const {
|
||||
configApi,
|
||||
discoveryApi,
|
||||
environment,
|
||||
provider,
|
||||
joinScopes = defaultJoinScopes,
|
||||
oauthRequestApi,
|
||||
authFlow,
|
||||
sessionTransform = id => id,
|
||||
} = options;
|
||||
|
||||
this.authRequester = oauthRequestApi.createAuthRequester({
|
||||
provider,
|
||||
onAuthRequest: async scopes => {
|
||||
if (authFlow === 'popup') {
|
||||
const enableExperimentalRedirectFlow =
|
||||
this.configApi.getOptionalBoolean('enableExperimentalRedirectFlow') ??
|
||||
false;
|
||||
if (!enableExperimentalRedirectFlow) {
|
||||
return this.showPopup(scopes);
|
||||
}
|
||||
return this.executeRedirect(scopes);
|
||||
},
|
||||
authFlow,
|
||||
});
|
||||
|
||||
this.authFlow = authFlow;
|
||||
this.configApi = configApi;
|
||||
this.discoveryApi = discoveryApi;
|
||||
this.environment = environment;
|
||||
this.provider = provider;
|
||||
@@ -106,7 +107,11 @@ export class DefaultAuthConnector<AuthSession>
|
||||
}
|
||||
|
||||
async createSession(options: CreateSessionOptions): Promise<AuthSession> {
|
||||
if (options.instantPopup && this.authFlow === 'popup') {
|
||||
const enableExperimentalRedirectFlow =
|
||||
this.configApi.getOptionalBoolean('enableExperimentalRedirectFlow') ??
|
||||
false;
|
||||
|
||||
if (options.instantPopup && !enableExperimentalRedirectFlow) {
|
||||
return this.showPopup(options.scopes);
|
||||
}
|
||||
return this.authRequester(options.scopes);
|
||||
@@ -184,14 +189,13 @@ export class DefaultAuthConnector<AuthSession>
|
||||
|
||||
private async executeRedirect(scopes: Set<string>): Promise<AuthSession> {
|
||||
const scope = this.joinScopesFunc(scopes);
|
||||
const redirectUrl = await this.buildUrl('/start', {
|
||||
// redirect to auth api
|
||||
window.location.href = await this.buildUrl('/start', {
|
||||
scope,
|
||||
origin: window.location.origin,
|
||||
redirectUrl: window.location.href,
|
||||
authFlow: 'redirect',
|
||||
flow: 'redirect',
|
||||
});
|
||||
// redirect to auth api
|
||||
window.location.href = redirectUrl;
|
||||
// return a promise that never resolves
|
||||
return new Promise(() => {});
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { makeStyles, Theme } from '@material-ui/core/styles';
|
||||
import Dialog from '@material-ui/core/Dialog';
|
||||
import DialogActions from '@material-ui/core/DialogActions';
|
||||
@@ -24,7 +23,11 @@ import Button from '@material-ui/core/Button';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import useObservable from 'react-use/lib/useObservable';
|
||||
import LoginRequestListItem from './LoginRequestListItem';
|
||||
import { useApi, oauthRequestApiRef } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
useApi,
|
||||
configApiRef,
|
||||
oauthRequestApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
|
||||
export type OAuthRequestDialogClassKey =
|
||||
@@ -58,10 +61,13 @@ export function OAuthRequestDialog(_props: {}) {
|
||||
const classes = useStyles();
|
||||
const [busy, setBusy] = useState(false);
|
||||
const oauthRequestApi = useApi(oauthRequestApiRef);
|
||||
const redirectMessage =
|
||||
oauthRequestApi.authFlow() === 'popup'
|
||||
? ''
|
||||
: 'This will trigger a http redirect to OAuth Login.';
|
||||
const configApi = useApi(configApiRef);
|
||||
|
||||
const authRedirect =
|
||||
configApi.getOptionalBoolean('enableExperimentalRedirectFlow') ?? false;
|
||||
const redirectMessage = authRedirect
|
||||
? 'This will trigger a http redirect to OAuth Login.'
|
||||
: '';
|
||||
|
||||
const requests = useObservable(
|
||||
useMemo(() => oauthRequestApi.authRequest$(), [oauthRequestApi]),
|
||||
|
||||
@@ -548,7 +548,6 @@ export type OAuthRequestApi = {
|
||||
options: OAuthRequesterOptions<OAuthResponse>,
|
||||
): OAuthRequester<OAuthResponse>;
|
||||
authRequest$(): Observable<PendingOAuthRequest[]>;
|
||||
authFlow(): string;
|
||||
};
|
||||
|
||||
// @public
|
||||
@@ -563,7 +562,6 @@ export type OAuthRequester<TAuthResponse> = (
|
||||
export type OAuthRequesterOptions<TOAuthResponse> = {
|
||||
provider: AuthProviderInfo;
|
||||
onAuthRequest(scopes: Set<string>): Promise<TOAuthResponse>;
|
||||
authFlow: string;
|
||||
};
|
||||
|
||||
// @public
|
||||
|
||||
@@ -35,11 +35,6 @@ export type OAuthRequesterOptions<TOAuthResponse> = {
|
||||
* trigger() is called on an auth requests.
|
||||
*/
|
||||
onAuthRequest(scopes: Set<string>): Promise<TOAuthResponse>;
|
||||
|
||||
/**
|
||||
* The authentication flow type
|
||||
*/
|
||||
authFlow: string;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -124,11 +119,6 @@ export type OAuthRequestApi = {
|
||||
* AuthRequester calls will resolve to the value returned by the onAuthRequest call.
|
||||
*/
|
||||
authRequest$(): Observable<PendingOAuthRequest[]>;
|
||||
|
||||
/**
|
||||
* The authentication flow type
|
||||
*/
|
||||
authFlow(): string;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -89,6 +89,7 @@ export const defaultApis = [
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
|
||||
GoogleAuth.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
@@ -103,6 +104,7 @@ export const defaultApis = [
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
|
||||
MicrosoftAuth.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
@@ -117,6 +119,7 @@ export const defaultApis = [
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
|
||||
GithubAuth.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
defaultScopes: ['read:user'],
|
||||
@@ -132,6 +135,7 @@ export const defaultApis = [
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
|
||||
OktaAuth.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
@@ -146,6 +150,7 @@ export const defaultApis = [
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
|
||||
GitlabAuth.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
@@ -160,6 +165,7 @@ export const defaultApis = [
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
|
||||
OneLoginAuth.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
@@ -174,6 +180,7 @@ export const defaultApis = [
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
|
||||
BitbucketAuth.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
defaultScopes: ['team'],
|
||||
@@ -189,6 +196,7 @@ export const defaultApis = [
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) => {
|
||||
return AtlassianAuth.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
|
||||
@@ -103,7 +103,7 @@ export class OAuthAdapter implements AuthProviderRouteHandlers {
|
||||
const env = req.query.env?.toString();
|
||||
const origin = req.query.origin?.toString();
|
||||
const redirectUrl = req.query.redirectUrl?.toString();
|
||||
const flow = req.query.authFlow?.toString();
|
||||
const flow = req.query.flow?.toString();
|
||||
if (!env) {
|
||||
throw new InputError('No env provided in request query parameters');
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"clean": "backstage-cli package clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/config": "workspace:^",
|
||||
"@backstage/core-components": "workspace:^",
|
||||
"@backstage/core-plugin-api": "workspace:^",
|
||||
"@backstage/theme": "workspace:^",
|
||||
|
||||
@@ -18,6 +18,8 @@ import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils';
|
||||
import React from 'react';
|
||||
import { gitOpsApiRef, GitOpsRestApi } from '../../api';
|
||||
import ProfileCatalog from './ProfileCatalog';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { ConfigApi } from '@backstage/core-plugin-api';
|
||||
|
||||
import {
|
||||
ApiProvider,
|
||||
@@ -31,6 +33,9 @@ import { githubAuthApiRef } from '@backstage/core-plugin-api';
|
||||
describe('ProfileCatalog', () => {
|
||||
it('should render', async () => {
|
||||
const oauthRequestApi = new OAuthRequestManager();
|
||||
const configApi: ConfigApi = new ConfigReader({
|
||||
enableExperimentalRedirectFlow: false,
|
||||
});
|
||||
const apis = TestApiRegistry.from(
|
||||
[gitOpsApiRef, new GitOpsRestApi('http://localhost:3008')],
|
||||
[
|
||||
@@ -40,6 +45,7 @@ describe('ProfileCatalog', () => {
|
||||
'http://example.com/{{pluginId}}',
|
||||
),
|
||||
oauthRequestApi,
|
||||
configApi: configApi,
|
||||
}),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -6195,6 +6195,7 @@ __metadata:
|
||||
resolution: "@backstage/plugin-gitops-profiles@workspace:plugins/gitops-profiles"
|
||||
dependencies:
|
||||
"@backstage/cli": "workspace:^"
|
||||
"@backstage/config": "workspace:^"
|
||||
"@backstage/core-app-api": "workspace:^"
|
||||
"@backstage/core-components": "workspace:^"
|
||||
"@backstage/core-plugin-api": "workspace:^"
|
||||
|
||||
Reference in New Issue
Block a user