diff --git a/.changeset/sharp-jars-flash.md b/.changeset/sharp-jars-flash.md new file mode 100644 index 0000000000..41a3f1e39e --- /dev/null +++ b/.changeset/sharp-jars-flash.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-app-api': minor +--- + +add login in popup options to config popup width and height diff --git a/docs/auth/oidc.md b/docs/auth/oidc.md index 1882cb73f1..de369660dd 100644 --- a/docs/auth/oidc.md +++ b/docs/auth/oidc.md @@ -101,6 +101,19 @@ export const apis: AnyApiFactory[] = [ }, environment: configApi.getOptionalString('auth.environment'), defaultScopes: ['openid', 'profile', 'email'], + popupOptions: { + // optional, used to customize login in popup size + size: { + fullscreen: true, + }, + /** + * or specify popup width and height + * size: { + width: 1000, + height: 1000, + } + */ + }, }), }), /* highlight-add-end */ diff --git a/packages/core-app-api/api-report.md b/packages/core-app-api/api-report.md index 09efe516b9..5746598ffd 100644 --- a/packages/core-app-api/api-report.md +++ b/packages/core-app-api/api-report.md @@ -526,6 +526,7 @@ export class OAuth2 // @public export type OAuth2CreateOptions = OAuthApiCreateOptions & { scopeTransform?: (scopes: string[]) => string[]; + popupOptions?: PopupOptions; }; // @public @@ -577,6 +578,21 @@ export type OneLoginAuthCreateOptions = { provider?: AuthProviderInfo; }; +// @public +export type PopupOptions = { + size?: + | { + width: number; + height: number; + fullscreen?: never; + } + | { + width?: never; + height?: never; + fullscreen: boolean; + }; +}; + // @public export class SamlAuth implements ProfileInfoApi, BackstageIdentityApi, SessionApi 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 44bd5269ea..fd40a9c8fb 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 @@ -14,7 +14,10 @@ * limitations under the License. */ -import { DefaultAuthConnector } from '../../../../lib/AuthConnector'; +import { + DefaultAuthConnector, + PopupOptions, +} from '../../../../lib/AuthConnector'; import { RefreshingAuthSessionManager } from '../../../../lib/AuthSessionManager'; import { SessionManager } from '../../../../lib/AuthSessionManager/types'; import { @@ -38,6 +41,7 @@ import { OAuthApiCreateOptions } from '../types'; */ export type OAuth2CreateOptions = OAuthApiCreateOptions & { scopeTransform?: (scopes: string[]) => string[]; + popupOptions?: PopupOptions; }; export type OAuth2Response = { @@ -79,6 +83,7 @@ export default class OAuth2 oauthRequestApi, defaultScopes = [], scopeTransform = x => x, + popupOptions, } = options; const connector = new DefaultAuthConnector({ @@ -103,6 +108,7 @@ export default class OAuth2 }, }; }, + popupOptions, }); const sessionManager = new RefreshingAuthSessionManager({ diff --git a/packages/core-app-api/src/apis/implementations/auth/oauth2/types.ts b/packages/core-app-api/src/apis/implementations/auth/oauth2/types.ts index 242cd94154..bd3bb95ba6 100644 --- a/packages/core-app-api/src/apis/implementations/auth/oauth2/types.ts +++ b/packages/core-app-api/src/apis/implementations/auth/oauth2/types.ts @@ -20,6 +20,7 @@ import { } from '@backstage/core-plugin-api'; export type { OAuth2CreateOptions } from './OAuth2'; +export type { PopupOptions } from '../../../../lib/AuthConnector'; /** * Session information for generic OAuth2 auth. * 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 c46e87581f..4273ea99ee 100644 --- a/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts +++ b/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts @@ -167,6 +167,80 @@ describe('DefaultAuthConnector', () => { await expect(sessionPromise).resolves.toBe('my-session'); expect(popupSpy).toHaveBeenCalledTimes(1); + expect(popupSpy).toHaveBeenCalledWith({ + name: 'My Provider Login', + origin: 'http://my-host', + url: 'http://my-host/api/auth/my-provider/start?scope=&origin=http%3A%2F%2Flocalhost&flow=popup&env=production', + width: 450, + height: 730, + }); + }); + + it('should show popup fullscreen', async () => { + const popupSpy = jest + .spyOn(loginPopup, 'showLoginPopup') + .mockResolvedValue('my-session'); + + jest.spyOn(window.screen, 'width', 'get').mockReturnValue(1000); + jest.spyOn(window.screen, 'height', 'get').mockReturnValue(1000); + + const connector = new DefaultAuthConnector({ + ...defaultOptions, + oauthRequestApi: new MockOAuthApi(), + sessionTransform: str => str, + popupOptions: { + size: { + fullscreen: true, + }, + }, + }); + + const sessionPromise = connector.createSession({ + scopes: new Set(), + instantPopup: true, + }); + + await expect(sessionPromise).resolves.toBe('my-session'); + + expect(popupSpy).toHaveBeenCalledWith({ + height: 1000, + name: 'My Provider Login', + origin: 'http://my-host', + url: 'http://my-host/api/auth/my-provider/start?scope=&origin=http%3A%2F%2Flocalhost&flow=popup&env=production', + width: 1000, + }); + }); + + it('should show popup with special width and height', async () => { + const popupSpy = jest + .spyOn(loginPopup, 'showLoginPopup') + .mockResolvedValue('my-session'); + const connector = new DefaultAuthConnector({ + ...defaultOptions, + oauthRequestApi: new MockOAuthApi(), + sessionTransform: str => str, + popupOptions: { + size: { + width: 500, + height: 1000, + }, + }, + }); + + const sessionPromise = connector.createSession({ + scopes: new Set(), + instantPopup: true, + }); + + await expect(sessionPromise).resolves.toBe('my-session'); + + expect(popupSpy).toHaveBeenCalledWith({ + name: 'My Provider Login', + origin: 'http://my-host', + url: 'http://my-host/api/auth/my-provider/start?scope=&origin=http%3A%2F%2Flocalhost&flow=popup&env=production', + width: 500, + height: 1000, + }); }); it('should use join func to join scopes', async () => { diff --git a/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.ts b/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.ts index 728ed3878c..ad1b25619d 100644 --- a/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.ts +++ b/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.ts @@ -21,7 +21,7 @@ import { OAuthRequester, } from '@backstage/core-plugin-api'; import { showLoginPopup } from '../loginPopup'; -import { AuthConnector, CreateSessionOptions } from './types'; +import { AuthConnector, CreateSessionOptions, PopupOptions } from './types'; let warned = false; @@ -55,6 +55,10 @@ type Options = { * ConfigApi instance used to configure authentication flow of pop-up or redirect. */ configApi?: ConfigApi; + /** + * Options used to configure auth popup + */ + popupOptions?: PopupOptions; }; function defaultJoinScopes(scopes: Set) { @@ -76,6 +80,7 @@ export class DefaultAuthConnector private readonly authRequester: OAuthRequester; private readonly sessionTransform: (response: any) => Promise; private readonly enableExperimentalRedirectFlow: boolean; + private readonly popupOptions: PopupOptions | undefined; constructor(options: Options) { const { configApi, @@ -85,6 +90,7 @@ export class DefaultAuthConnector joinScopes = defaultJoinScopes, oauthRequestApi, sessionTransform = id => id, + popupOptions, } = options; if (!warned && !configApi) { @@ -114,6 +120,7 @@ export class DefaultAuthConnector this.provider = provider; this.joinScopesFunc = joinScopes; this.sessionTransform = sessionTransform; + this.popupOptions = popupOptions; } async createSession(options: CreateSessionOptions): Promise { @@ -188,12 +195,20 @@ export class DefaultAuthConnector flow: 'popup', }); + const width = this.popupOptions?.size?.fullscreen + ? window.screen.width + : this.popupOptions?.size?.width || 450; + + const height = this.popupOptions?.size?.fullscreen + ? window.screen.height + : this.popupOptions?.size?.height || 730; + const payload = await showLoginPopup({ url: popupUrl, name: `${this.provider.title} Login`, origin: new URL(popupUrl).origin, - width: 450, - height: 730, + width, + height, }); return await this.sessionTransform(payload); diff --git a/packages/core-app-api/src/lib/AuthConnector/types.ts b/packages/core-app-api/src/lib/AuthConnector/types.ts index c8e83e1ccc..3c01bb254f 100644 --- a/packages/core-app-api/src/lib/AuthConnector/types.ts +++ b/packages/core-app-api/src/lib/AuthConnector/types.ts @@ -28,3 +28,13 @@ export type AuthConnector = { refreshSession(scopes?: Set): Promise; removeSession(): Promise; }; + +/** + * Options for login popup + * @public + */ +export type PopupOptions = { + size?: + | { width: number; height: number; fullscreen?: never } + | { width?: never; height?: never; fullscreen: boolean }; +};