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:
headphonejames
2023-02-27 16:34:30 -08:00
parent 3ae55c4a8d
commit 5acc047748
33 changed files with 143 additions and 87 deletions
-1
View File
@@ -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
+8 -8
View File
@@ -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'),
});
},
}),
+3 -4
View File
@@ -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;
};
+7 -6
View File
@@ -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 = [
@@ -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]),
-2
View File
@@ -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');
}
+1
View File
@@ -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,
}),
],
);
+1
View File
@@ -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:^"