diff --git a/app-config.yaml b/app-config.yaml index 350b9068e3..dd72051189 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -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 diff --git a/packages/app-defaults/src/defaults/apis.ts b/packages/app-defaults/src/defaults/apis.ts index feb4cd6cc8..b3a3d55736 100644 --- a/packages/app-defaults/src/defaults/apis.ts +++ b/packages/app-defaults/src/defaults/apis.ts @@ -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'), }); }, }), diff --git a/packages/core-app-api/api-report.md b/packages/core-app-api/api-report.md index f8d5a99cd6..f7e2155db0 100644 --- a/packages/core-app-api/api-report.md +++ b/packages/core-app-api/api-report.md @@ -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; // (undocumented) @@ -516,10 +515,10 @@ export class OneLoginAuth { // @public export type OneLoginAuthCreateOptions = { + configApi: ConfigApi; discoveryApi: DiscoveryApi; oauthRequestApi: OAuthRequestApi; environment?: string; - authFlow?: string; provider?: AuthProviderInfo; }; diff --git a/packages/core-app-api/config.d.ts b/packages/core-app-api/config.d.ts index c499dfa7c6..2de1d96114 100644 --- a/packages/core-app-api/config.d.ts +++ b/packages/core-app-api/config.d.ts @@ -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; } diff --git a/packages/core-app-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.test.ts b/packages/core-app-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.test.ts index a22fc24626..a64fadeb3e 100644 --- a/packages/core-app-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.test.ts +++ b/packages/core-app-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.test.ts @@ -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 = [ diff --git a/packages/core-app-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.test.ts b/packages/core-app-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.test.ts index 54cfc8e41b..4dd6af715e 100644 --- a/packages/core-app-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.test.ts +++ b/packages/core-app-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.test.ts @@ -30,7 +30,6 @@ describe('OAuthRequestManager', () => { icon: () => null, }, onAuthRequest: async () => 'hello', - authFlow: 'popup', }); expect(reqSpy).toHaveBeenCalledTimes(0); diff --git a/packages/core-app-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.ts b/packages/core-app-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.ts index 751f2bf24c..5d467be7b4 100644 --- a/packages/core-app-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.ts +++ b/packages/core-app-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.ts @@ -37,10 +37,8 @@ export class OAuthRequestManager implements OAuthRequestApi { private readonly subject = new BehaviorSubject([]); private currentRequests: PendingOAuthRequest[] = []; private handlerCount = 0; - private authFlowStr: string = 'popup'; createAuthRequester(options: OAuthRequesterOptions): OAuthRequester { - this.authFlowStr = options.authFlow; const handler = new OAuthPendingRequests(); const index = this.handlerCount; @@ -93,8 +91,4 @@ export class OAuthRequestManager implements OAuthRequestApi { authRequest$(): Observable { return this.subject; } - - authFlow(): string { - return this.authFlowStr; - } } diff --git a/packages/core-app-api/src/apis/implementations/auth/atlassian/AtlassianAuth.ts b/packages/core-app-api/src/apis/implementations/auth/atlassian/AtlassianAuth.ts index df714e2290..3477eec361 100644 --- a/packages/core-app-api/src/apis/implementations/auth/atlassian/AtlassianAuth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/atlassian/AtlassianAuth.ts @@ -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, }); } } diff --git a/packages/core-app-api/src/apis/implementations/auth/bitbucket/BitbucketAuth.test.ts b/packages/core-app-api/src/apis/implementations/auth/bitbucket/BitbucketAuth.test.ts index f4bd7cbe85..29cd696a29 100644 --- a/packages/core-app-api/src/apis/implementations/auth/bitbucket/BitbucketAuth.test.ts +++ b/packages/core-app-api/src/apis/implementations/auth/bitbucket/BitbucketAuth.test.ts @@ -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); diff --git a/packages/core-app-api/src/apis/implementations/auth/bitbucket/BitbucketAuth.ts b/packages/core-app-api/src/apis/implementations/auth/bitbucket/BitbucketAuth.ts index 3bdb09b010..7a9f1bc094 100644 --- a/packages/core-app-api/src/apis/implementations/auth/bitbucket/BitbucketAuth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/bitbucket/BitbucketAuth.ts @@ -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, }); } diff --git a/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.test.ts b/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.test.ts index ea7b4ac688..e34c3cea87 100644 --- a/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.test.ts +++ b/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.test.ts @@ -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'), }); diff --git a/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.ts b/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.ts index 5acdd20719..943dfa7da4 100644 --- a/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.ts @@ -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, }); } } diff --git a/packages/core-app-api/src/apis/implementations/auth/gitlab/GitlabAuth.test.ts b/packages/core-app-api/src/apis/implementations/auth/gitlab/GitlabAuth.test.ts index 3346af9b7c..71368c173e 100644 --- a/packages/core-app-api/src/apis/implementations/auth/gitlab/GitlabAuth.test.ts +++ b/packages/core-app-api/src/apis/implementations/auth/gitlab/GitlabAuth.test.ts @@ -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'), }); diff --git a/packages/core-app-api/src/apis/implementations/auth/gitlab/GitlabAuth.ts b/packages/core-app-api/src/apis/implementations/auth/gitlab/GitlabAuth.ts index 919d67dd8d..c39f5a6e23 100644 --- a/packages/core-app-api/src/apis/implementations/auth/gitlab/GitlabAuth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/gitlab/GitlabAuth.ts @@ -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, }); } diff --git a/packages/core-app-api/src/apis/implementations/auth/google/GoogleAuth.test.ts b/packages/core-app-api/src/apis/implementations/auth/google/GoogleAuth.test.ts index f8a1c3a1f5..44a3fed30f 100644 --- a/packages/core-app-api/src/apis/implementations/auth/google/GoogleAuth.test.ts +++ b/packages/core-app-api/src/apis/implementations/auth/google/GoogleAuth.test.ts @@ -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'), }); diff --git a/packages/core-app-api/src/apis/implementations/auth/google/GoogleAuth.ts b/packages/core-app-api/src/apis/implementations/auth/google/GoogleAuth.ts index e8adefb08f..f4674ab64f 100644 --- a/packages/core-app-api/src/apis/implementations/auth/google/GoogleAuth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/google/GoogleAuth.ts @@ -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 => { diff --git a/packages/core-app-api/src/apis/implementations/auth/microsoft/MicrosoftAuth.ts b/packages/core-app-api/src/apis/implementations/auth/microsoft/MicrosoftAuth.ts index c28e115950..30cfde7c6e 100644 --- a/packages/core-app-api/src/apis/implementations/auth/microsoft/MicrosoftAuth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/microsoft/MicrosoftAuth.ts @@ -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, }); } diff --git a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.test.ts b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.test.ts index 0bb40c23fc..671d80c2be 100644 --- a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.test.ts +++ b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.test.ts @@ -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'), diff --git a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts index 7a133154f2..e3ff395fd0 100644 --- a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts +++ b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts @@ -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, diff --git a/packages/core-app-api/src/apis/implementations/auth/okta/OktaAuth.test.ts b/packages/core-app-api/src/apis/implementations/auth/okta/OktaAuth.test.ts index 6489b326ff..1918e51a34 100644 --- a/packages/core-app-api/src/apis/implementations/auth/okta/OktaAuth.test.ts +++ b/packages/core-app-api/src/apis/implementations/auth/okta/OktaAuth.test.ts @@ -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'), }); diff --git a/packages/core-app-api/src/apis/implementations/auth/okta/OktaAuth.ts b/packages/core-app-api/src/apis/implementations/auth/okta/OktaAuth.ts index 9075d8cb82..c1921a8f93 100644 --- a/packages/core-app-api/src/apis/implementations/auth/okta/OktaAuth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/okta/OktaAuth.ts @@ -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 => { diff --git a/packages/core-app-api/src/apis/implementations/auth/onelogin/OneLoginAuth.ts b/packages/core-app-api/src/apis/implementations/auth/onelogin/OneLoginAuth.ts index edfcbc4463..8beac05755 100644 --- a/packages/core-app-api/src/apis/implementations/auth/onelogin/OneLoginAuth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/onelogin/OneLoginAuth.ts @@ -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 => { diff --git a/packages/core-app-api/src/apis/implementations/auth/types.ts b/packages/core-app-api/src/apis/implementations/auth/types.ts index fdb2bbc7f8..c2614a54ff 100644 --- a/packages/core-app-api/src/apis/implementations/auth/types.ts +++ b/packages/core-app-api/src/apis/implementations/auth/types.ts @@ -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; }; diff --git a/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts b/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts index fd2a2833a8..8234f3cc51 100644 --- a/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts +++ b/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts @@ -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', () => { diff --git a/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.ts b/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.ts index 7cada8b0f9..3ec997d80a 100644 --- a/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.ts +++ b/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.ts @@ -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 = { */ sessionTransform?(response: any): AuthSession | Promise; /** - * 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) { @@ -68,36 +68,37 @@ export class DefaultAuthConnector implements AuthConnector { private readonly discoveryApi: DiscoveryApi; + private readonly configApi: ConfigApi; private readonly environment: string; private readonly provider: AuthProviderInfo; private readonly joinScopesFunc: (scopes: Set) => string; private readonly authRequester: OAuthRequester; private readonly sessionTransform: (response: any) => Promise; - private readonly authFlow: string; - constructor(options: Options) { 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 } async createSession(options: CreateSessionOptions): Promise { - 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 private async executeRedirect(scopes: Set): Promise { 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(() => {}); } diff --git a/packages/core-components/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx b/packages/core-components/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx index e68120298a..c7a8e49faa 100644 --- a/packages/core-components/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx +++ b/packages/core-components/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx @@ -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]), diff --git a/packages/core-plugin-api/api-report.md b/packages/core-plugin-api/api-report.md index 17278d4cc5..6bba47a3f4 100644 --- a/packages/core-plugin-api/api-report.md +++ b/packages/core-plugin-api/api-report.md @@ -548,7 +548,6 @@ export type OAuthRequestApi = { options: OAuthRequesterOptions, ): OAuthRequester; authRequest$(): Observable; - authFlow(): string; }; // @public @@ -563,7 +562,6 @@ export type OAuthRequester = ( export type OAuthRequesterOptions = { provider: AuthProviderInfo; onAuthRequest(scopes: Set): Promise; - authFlow: string; }; // @public diff --git a/packages/core-plugin-api/src/apis/definitions/OAuthRequestApi.ts b/packages/core-plugin-api/src/apis/definitions/OAuthRequestApi.ts index 2027852de7..75bf3a3864 100644 --- a/packages/core-plugin-api/src/apis/definitions/OAuthRequestApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/OAuthRequestApi.ts @@ -35,11 +35,6 @@ export type OAuthRequesterOptions = { * trigger() is called on an auth requests. */ onAuthRequest(scopes: Set): Promise; - - /** - * 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; - - /** - * The authentication flow type - */ - authFlow(): string; }; /** diff --git a/packages/test-utils/src/testUtils/defaultApis.ts b/packages/test-utils/src/testUtils/defaultApis.ts index 1ff10bfe6f..2231d28cd0 100644 --- a/packages/test-utils/src/testUtils/defaultApis.ts +++ b/packages/test-utils/src/testUtils/defaultApis.ts @@ -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'), diff --git a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts index 78365cda0f..3d2a312a81 100644 --- a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts +++ b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts @@ -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'); } diff --git a/plugins/gitops-profiles/package.json b/plugins/gitops-profiles/package.json index b485aea867..9c8f623c12 100644 --- a/plugins/gitops-profiles/package.json +++ b/plugins/gitops-profiles/package.json @@ -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:^", diff --git a/plugins/gitops-profiles/src/components/ProfileCatalog/ProfileCatalog.test.tsx b/plugins/gitops-profiles/src/components/ProfileCatalog/ProfileCatalog.test.tsx index 5aa59a1cae..03d3d84c36 100644 --- a/plugins/gitops-profiles/src/components/ProfileCatalog/ProfileCatalog.test.tsx +++ b/plugins/gitops-profiles/src/components/ProfileCatalog/ProfileCatalog.test.tsx @@ -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, }), ], ); diff --git a/yarn.lock b/yarn.lock index 7cc0dbd8b5..974db36e32 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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:^"