updates after code review
Signed-off-by: Gasan Guseinov <gasan.guseinov@ing.com>
This commit is contained in:
@@ -1,5 +1,17 @@
|
||||
---
|
||||
'@backstage/core-app-api': patch
|
||||
'@backstage/core-app-api': minor
|
||||
---
|
||||
|
||||
custom AuthConnector for OAuth2
|
||||
Support custom `AuthConnector` for `OAuth2`.
|
||||
|
||||
A user can pass their own `AuthConnector` implementation in `OAuth2` constructor.
|
||||
In which case the session manager will use that instead of the `DefaultAuthConnector` to interact with the
|
||||
authentication provider.
|
||||
|
||||
A custom `AuthConnector` may call the authentication provider from the front-end, store and retrieve tokens
|
||||
in the session storage, for example, and otherwise send custom requests to the authentication provider and
|
||||
handle its responses.
|
||||
|
||||
Note, that if the custom `AuthConnector` transforms scopes returned from the authentication provider,
|
||||
the transformation must be the same as `OAuth2CreateOptions#scopeTransform` passed to `OAuth2` constructor.
|
||||
See creating `DefaultAuthConnector` in `OAuth2#create(...)` for an example.
|
||||
|
||||
@@ -296,11 +296,26 @@ export type AuthApiCreateOptions = {
|
||||
|
||||
// @public
|
||||
export type AuthConnector<AuthSession> = {
|
||||
createSession(options: CreateSessionOptions): Promise<AuthSession>;
|
||||
refreshSession(scopes?: Set<string>): Promise<AuthSession>;
|
||||
createSession(
|
||||
options: AuthConnectorCreateSessionOptions,
|
||||
): Promise<AuthSession>;
|
||||
refreshSession(
|
||||
options?: AuthConnectorRefreshSessionOptions,
|
||||
): Promise<AuthSession>;
|
||||
removeSession(): Promise<void>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type AuthConnectorCreateSessionOptions = {
|
||||
scopes: Set<string>;
|
||||
instantPopup?: boolean;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type AuthConnectorRefreshSessionOptions = {
|
||||
scopes: Set<string>;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type BackstageApp = {
|
||||
getPlugins(): BackstagePlugin[];
|
||||
@@ -360,12 +375,6 @@ export function createFetchApi(options: {
|
||||
middleware?: FetchMiddleware | FetchMiddleware[] | undefined;
|
||||
}): FetchApi;
|
||||
|
||||
// @public (undocumented)
|
||||
export type CreateSessionOptions = {
|
||||
scopes: Set<string>;
|
||||
instantPopup?: boolean;
|
||||
};
|
||||
|
||||
// @public
|
||||
export function createSpecializedApp(options: AppOptions): BackstageApp;
|
||||
|
||||
@@ -491,15 +500,6 @@ export class LocalStorageFeatureFlags implements FeatureFlagsApi {
|
||||
save(options: FeatureFlagsSaveOptions): void;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type LoginPopupOptions = {
|
||||
url: string;
|
||||
name: string;
|
||||
origin: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
};
|
||||
|
||||
// @public
|
||||
export class MicrosoftAuth {
|
||||
// (undocumented)
|
||||
@@ -573,9 +573,7 @@ export class OAuth2
|
||||
export type OAuth2CreateOptions = OAuthApiCreateOptions & {
|
||||
scopeTransform?: (scopes: string[]) => string[];
|
||||
popupOptions?: PopupOptions;
|
||||
authConnectorFactory?: (
|
||||
opts: OAuth2CreateOptions,
|
||||
) => AuthConnector<OAuth2Session>;
|
||||
authConnector?: AuthConnector<OAuth2Session>;
|
||||
};
|
||||
|
||||
// @public
|
||||
@@ -627,6 +625,19 @@ export type OneLoginAuthCreateOptions = {
|
||||
provider?: AuthProviderInfo;
|
||||
};
|
||||
|
||||
// @public
|
||||
export function openLoginPopup(
|
||||
options: OpenLoginPopupOptions,
|
||||
): Promise<unknown>;
|
||||
|
||||
// @public
|
||||
export type OpenLoginPopupOptions = {
|
||||
url: string;
|
||||
name: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type PopupOptions = {
|
||||
size?:
|
||||
@@ -662,9 +673,6 @@ export class SamlAuth
|
||||
signOut(): Promise<void>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export function showLoginPopup(options: LoginPopupOptions): Promise<any>;
|
||||
|
||||
// @public
|
||||
export type SignInPageProps = PropsWithChildren<{
|
||||
onSignInSuccess(identityApi: IdentityApi): void;
|
||||
|
||||
@@ -44,9 +44,7 @@ import { OAuthApiCreateOptions } from '../types';
|
||||
export type OAuth2CreateOptions = OAuthApiCreateOptions & {
|
||||
scopeTransform?: (scopes: string[]) => string[];
|
||||
popupOptions?: PopupOptions;
|
||||
authConnectorFactory?: (
|
||||
opts: OAuth2CreateOptions,
|
||||
) => AuthConnector<OAuth2Session>;
|
||||
authConnector?: AuthConnector<OAuth2Session>;
|
||||
};
|
||||
|
||||
export type OAuth2Response = {
|
||||
@@ -83,72 +81,61 @@ export default class OAuth2
|
||||
BackstageIdentityApi,
|
||||
SessionApi
|
||||
{
|
||||
private static createDefaultAuthConnector(options: OAuth2CreateOptions) {
|
||||
const {
|
||||
configApi,
|
||||
discoveryApi,
|
||||
environment,
|
||||
provider,
|
||||
oauthRequestApi,
|
||||
scopeTransform,
|
||||
popupOptions,
|
||||
} = options;
|
||||
|
||||
return new DefaultAuthConnector({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
environment: environment!,
|
||||
provider: provider!,
|
||||
oauthRequestApi: oauthRequestApi,
|
||||
sessionTransform({
|
||||
backstageIdentity,
|
||||
...res
|
||||
}: OAuth2Response): OAuth2Session {
|
||||
const session: OAuth2Session = {
|
||||
...res,
|
||||
providerInfo: {
|
||||
idToken: res.providerInfo.idToken,
|
||||
accessToken: res.providerInfo.accessToken,
|
||||
scopes: OAuth2.normalizeScopes(
|
||||
scopeTransform!,
|
||||
res.providerInfo.scope,
|
||||
),
|
||||
expiresAt: res.providerInfo.expiresInSeconds
|
||||
? new Date(Date.now() + res.providerInfo.expiresInSeconds * 1000)
|
||||
: undefined,
|
||||
},
|
||||
};
|
||||
if (backstageIdentity) {
|
||||
session.backstageIdentity = {
|
||||
token: backstageIdentity.token,
|
||||
identity: backstageIdentity.identity,
|
||||
expiresAt: backstageIdentity.expiresInSeconds
|
||||
? new Date(Date.now() + backstageIdentity.expiresInSeconds * 1000)
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
return session;
|
||||
},
|
||||
popupOptions,
|
||||
});
|
||||
}
|
||||
|
||||
static create(options: OAuth2CreateOptions) {
|
||||
const {
|
||||
configApi,
|
||||
discoveryApi,
|
||||
environment = 'development',
|
||||
provider = DEFAULT_PROVIDER,
|
||||
oauthRequestApi,
|
||||
defaultScopes = [],
|
||||
scopeTransform = x => x,
|
||||
authConnectorFactory = (opts: OAuth2CreateOptions) =>
|
||||
OAuth2.createDefaultAuthConnector(opts),
|
||||
popupOptions,
|
||||
} = options;
|
||||
|
||||
const connector = authConnectorFactory({
|
||||
...options,
|
||||
scopeTransform,
|
||||
environment,
|
||||
provider,
|
||||
});
|
||||
const connector =
|
||||
options.authConnector ??
|
||||
new DefaultAuthConnector({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
environment,
|
||||
provider,
|
||||
oauthRequestApi: oauthRequestApi,
|
||||
sessionTransform({
|
||||
backstageIdentity,
|
||||
...res
|
||||
}: OAuth2Response): OAuth2Session {
|
||||
const session: OAuth2Session = {
|
||||
...res,
|
||||
providerInfo: {
|
||||
idToken: res.providerInfo.idToken,
|
||||
accessToken: res.providerInfo.accessToken,
|
||||
scopes: OAuth2.normalizeScopes(
|
||||
scopeTransform,
|
||||
res.providerInfo.scope,
|
||||
),
|
||||
expiresAt: res.providerInfo.expiresInSeconds
|
||||
? new Date(
|
||||
Date.now() + res.providerInfo.expiresInSeconds * 1000,
|
||||
)
|
||||
: undefined,
|
||||
},
|
||||
};
|
||||
if (backstageIdentity) {
|
||||
session.backstageIdentity = {
|
||||
token: backstageIdentity.token,
|
||||
identity: backstageIdentity.identity,
|
||||
expiresAt: backstageIdentity.expiresInSeconds
|
||||
? new Date(
|
||||
Date.now() + backstageIdentity.expiresInSeconds * 1000,
|
||||
)
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
return session;
|
||||
},
|
||||
popupOptions,
|
||||
});
|
||||
|
||||
const sessionManager = new RefreshingAuthSessionManager({
|
||||
connector,
|
||||
|
||||
@@ -23,7 +23,10 @@
|
||||
export * from './apis';
|
||||
export * from './app';
|
||||
export * from './routing';
|
||||
export type { AuthConnector } from './lib';
|
||||
export type { CreateSessionOptions } from './lib';
|
||||
export { showLoginPopup } from './lib';
|
||||
export type { LoginPopupOptions } from './lib';
|
||||
export type {
|
||||
AuthConnector,
|
||||
AuthConnectorCreateSessionOptions,
|
||||
AuthConnectorRefreshSessionOptions,
|
||||
openLoginPopup,
|
||||
OpenLoginPopupOptions,
|
||||
} from './lib';
|
||||
|
||||
@@ -26,7 +26,7 @@ import { ConfigApi } from '@backstage/core-plugin-api';
|
||||
|
||||
jest.mock('../loginPopup', () => {
|
||||
return {
|
||||
showLoginPopup: jest.fn(),
|
||||
openLoginPopup: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -74,7 +74,9 @@ describe('DefaultAuthConnector', () => {
|
||||
);
|
||||
|
||||
const connector = new DefaultAuthConnector<any>(defaultOptions);
|
||||
const session = await connector.refreshSession(new Set(['a', 'b', 'c']));
|
||||
const session = await connector.refreshSession({
|
||||
scopes: new Set(['a', 'b', 'c']),
|
||||
});
|
||||
expect(session.idToken).toBe('mock-id-token');
|
||||
expect(session.accessToken).toBe('mock-access-token');
|
||||
expect(session.scopes).toEqual(new Set(['a', 'b', 'c']));
|
||||
@@ -118,7 +120,7 @@ describe('DefaultAuthConnector', () => {
|
||||
it('should create a session', async () => {
|
||||
const mockOauth = new MockOAuthApi();
|
||||
const popupSpy = jest
|
||||
.spyOn(loginPopup, 'showLoginPopup')
|
||||
.spyOn(loginPopup, 'openLoginPopup')
|
||||
.mockResolvedValue({
|
||||
idToken: 'my-id-token',
|
||||
accessToken: 'my-access-token',
|
||||
@@ -151,7 +153,7 @@ describe('DefaultAuthConnector', () => {
|
||||
|
||||
it('should instantly show popup if option is set', async () => {
|
||||
const popupSpy = jest
|
||||
.spyOn(loginPopup, 'showLoginPopup')
|
||||
.spyOn(loginPopup, 'openLoginPopup')
|
||||
.mockResolvedValue('my-session');
|
||||
const connector = new DefaultAuthConnector({
|
||||
...defaultOptions,
|
||||
@@ -169,7 +171,6 @@ describe('DefaultAuthConnector', () => {
|
||||
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,
|
||||
@@ -178,7 +179,7 @@ describe('DefaultAuthConnector', () => {
|
||||
|
||||
it('should show popup fullscreen', async () => {
|
||||
const popupSpy = jest
|
||||
.spyOn(loginPopup, 'showLoginPopup')
|
||||
.spyOn(loginPopup, 'openLoginPopup')
|
||||
.mockResolvedValue('my-session');
|
||||
|
||||
jest.spyOn(window.screen, 'width', 'get').mockReturnValue(1000);
|
||||
@@ -205,7 +206,6 @@ describe('DefaultAuthConnector', () => {
|
||||
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,
|
||||
});
|
||||
@@ -213,7 +213,7 @@ describe('DefaultAuthConnector', () => {
|
||||
|
||||
it('should show popup with special width and height', async () => {
|
||||
const popupSpy = jest
|
||||
.spyOn(loginPopup, 'showLoginPopup')
|
||||
.spyOn(loginPopup, 'openLoginPopup')
|
||||
.mockResolvedValue('my-session');
|
||||
const connector = new DefaultAuthConnector({
|
||||
...defaultOptions,
|
||||
@@ -236,7 +236,6 @@ describe('DefaultAuthConnector', () => {
|
||||
|
||||
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,
|
||||
@@ -246,7 +245,7 @@ describe('DefaultAuthConnector', () => {
|
||||
it('should use join func to join scopes', async () => {
|
||||
const mockOauth = new MockOAuthApi();
|
||||
const popupSpy = jest
|
||||
.spyOn(loginPopup, 'showLoginPopup')
|
||||
.spyOn(loginPopup, 'openLoginPopup')
|
||||
.mockResolvedValue({ scopes: '' });
|
||||
const connector = new DefaultAuthConnector({
|
||||
...defaultOptions,
|
||||
|
||||
@@ -20,8 +20,13 @@ import {
|
||||
OAuthRequestApi,
|
||||
OAuthRequester,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { showLoginPopup } from '../loginPopup';
|
||||
import { AuthConnector, CreateSessionOptions, PopupOptions } from './types';
|
||||
import { openLoginPopup } from '../loginPopup';
|
||||
import {
|
||||
AuthConnector,
|
||||
AuthConnectorCreateSessionOptions,
|
||||
PopupOptions,
|
||||
AuthConnectorRefreshSessionOptions,
|
||||
} from './types';
|
||||
|
||||
let warned = false;
|
||||
|
||||
@@ -123,7 +128,9 @@ export class DefaultAuthConnector<AuthSession>
|
||||
this.popupOptions = popupOptions;
|
||||
}
|
||||
|
||||
async createSession(options: CreateSessionOptions): Promise<AuthSession> {
|
||||
async createSession(
|
||||
options: AuthConnectorCreateSessionOptions,
|
||||
): Promise<AuthSession> {
|
||||
if (options.instantPopup) {
|
||||
if (this.enableExperimentalRedirectFlow) {
|
||||
return this.executeRedirect(options.scopes);
|
||||
@@ -133,11 +140,13 @@ export class DefaultAuthConnector<AuthSession>
|
||||
return this.authRequester(options.scopes);
|
||||
}
|
||||
|
||||
async refreshSession(scopes?: Set<string>): Promise<any> {
|
||||
async refreshSession(
|
||||
options?: AuthConnectorRefreshSessionOptions,
|
||||
): Promise<any> {
|
||||
const res = await fetch(
|
||||
await this.buildUrl('/refresh', {
|
||||
optional: true,
|
||||
...(scopes && { scope: this.joinScopesFunc(scopes) }),
|
||||
...(options && { scope: this.joinScopesFunc(options.scopes) }),
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
@@ -203,10 +212,9 @@ export class DefaultAuthConnector<AuthSession>
|
||||
? window.screen.height
|
||||
: this.popupOptions?.size?.height || 730;
|
||||
|
||||
const payload = await showLoginPopup({
|
||||
const payload = await openLoginPopup({
|
||||
url: popupUrl,
|
||||
name: `${this.provider.title} Login`,
|
||||
origin: new URL(popupUrl).origin,
|
||||
width,
|
||||
height,
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { AuthProviderInfo, DiscoveryApi } from '@backstage/core-plugin-api';
|
||||
import { showLoginPopup } from '../loginPopup';
|
||||
import { openLoginPopup } from '../loginPopup';
|
||||
|
||||
type Options = {
|
||||
discoveryApi: DiscoveryApi;
|
||||
@@ -36,13 +36,12 @@ export class DirectAuthConnector<DirectAuthResponse> {
|
||||
|
||||
async createSession(): Promise<DirectAuthResponse> {
|
||||
const popupUrl = await this.buildUrl('/start');
|
||||
const payload = await showLoginPopup({
|
||||
const payload = (await openLoginPopup({
|
||||
url: popupUrl,
|
||||
name: `${this.provider.title} Login`,
|
||||
origin: new URL(popupUrl).origin,
|
||||
width: 450,
|
||||
height: 730,
|
||||
});
|
||||
})) as any;
|
||||
|
||||
return {
|
||||
...payload,
|
||||
|
||||
@@ -17,11 +17,18 @@
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type CreateSessionOptions = {
|
||||
export type AuthConnectorCreateSessionOptions = {
|
||||
scopes: Set<string>;
|
||||
instantPopup?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type AuthConnectorRefreshSessionOptions = {
|
||||
scopes: Set<string>;
|
||||
};
|
||||
|
||||
/**
|
||||
* An AuthConnector is responsible for realizing auth session actions
|
||||
* by for example communicating with a backend or interacting with the user.
|
||||
@@ -29,8 +36,12 @@ export type CreateSessionOptions = {
|
||||
* @public
|
||||
*/
|
||||
export type AuthConnector<AuthSession> = {
|
||||
createSession(options: CreateSessionOptions): Promise<AuthSession>;
|
||||
refreshSession(scopes?: Set<string>): Promise<AuthSession>;
|
||||
createSession(
|
||||
options: AuthConnectorCreateSessionOptions,
|
||||
): Promise<AuthSession>;
|
||||
refreshSession(
|
||||
options?: AuthConnectorRefreshSessionOptions,
|
||||
): Promise<AuthSession>;
|
||||
removeSession(): Promise<void>;
|
||||
};
|
||||
|
||||
|
||||
+22
-15
@@ -16,6 +16,7 @@
|
||||
|
||||
import { RefreshingAuthSessionManager } from './RefreshingAuthSessionManager';
|
||||
import { SessionState } from '@backstage/core-plugin-api';
|
||||
import { AuthConnectorRefreshSessionOptions } from '../AuthConnector';
|
||||
|
||||
const defaultOptions = {
|
||||
sessionScopes: (session: { scopes: Set<string> }) => session.scopes,
|
||||
@@ -47,7 +48,7 @@ describe('RefreshingAuthSessionManager', () => {
|
||||
await manager.getSession({});
|
||||
expect(createSession).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(refreshSession).toHaveBeenCalledWith(new Set());
|
||||
expect(refreshSession).toHaveBeenCalledWith({ scopes: new Set() });
|
||||
expect(stateSubscriber.mock.calls).toEqual([
|
||||
[SessionState.SignedOut],
|
||||
[SessionState.SignedIn],
|
||||
@@ -103,7 +104,7 @@ describe('RefreshingAuthSessionManager', () => {
|
||||
|
||||
await manager.getSession({ scopes: new Set(['a']) });
|
||||
expect(createSession).toHaveBeenCalledTimes(1);
|
||||
expect(refreshSession).toHaveBeenCalledWith(new Set(['a']));
|
||||
expect(refreshSession).toHaveBeenCalledWith({ scopes: new Set(['a']) });
|
||||
|
||||
await manager.getSession({ scopes: new Set(['a']) });
|
||||
expect(createSession).toHaveBeenCalledTimes(1);
|
||||
@@ -134,7 +135,7 @@ describe('RefreshingAuthSessionManager', () => {
|
||||
|
||||
expect(await manager.getSession({ optional: true })).toBe(undefined);
|
||||
expect(createSession).toHaveBeenCalledTimes(0);
|
||||
expect(refreshSession).toHaveBeenCalledWith(new Set());
|
||||
expect(refreshSession).toHaveBeenCalledWith({ scopes: new Set() });
|
||||
});
|
||||
|
||||
it('should forward option to instantly show auth popup and not attempt refresh', async () => {
|
||||
@@ -168,10 +169,12 @@ describe('RefreshingAuthSessionManager', () => {
|
||||
|
||||
it('should handle two simultaneous session refreshes with same scopes', async () => {
|
||||
const createSession = jest.fn();
|
||||
const refreshSession = jest.fn(async (scopes?: Set<string>) => ({
|
||||
scopes: scopes ?? new Set(),
|
||||
expired: false,
|
||||
}));
|
||||
const refreshSession = jest.fn(
|
||||
async (options?: AuthConnectorRefreshSessionOptions) => ({
|
||||
scopes: options?.scopes ?? new Set(),
|
||||
expired: false,
|
||||
}),
|
||||
);
|
||||
const manager = new RefreshingAuthSessionManager({
|
||||
connector: { createSession, refreshSession },
|
||||
...defaultOptions,
|
||||
@@ -192,10 +195,12 @@ describe('RefreshingAuthSessionManager', () => {
|
||||
|
||||
it('should handle two simultaneous session refreshes with different scopes', async () => {
|
||||
const createSession = jest.fn();
|
||||
const refreshSession = jest.fn(async (scopes?: Set<string>) => ({
|
||||
scopes: scopes ?? new Set(),
|
||||
expired: false,
|
||||
}));
|
||||
const refreshSession = jest.fn(
|
||||
async (options?: AuthConnectorRefreshSessionOptions) => ({
|
||||
scopes: options?.scopes ?? new Set(),
|
||||
expired: false,
|
||||
}),
|
||||
);
|
||||
const manager = new RefreshingAuthSessionManager({
|
||||
connector: { createSession, refreshSession },
|
||||
...defaultOptions,
|
||||
@@ -216,10 +221,12 @@ describe('RefreshingAuthSessionManager', () => {
|
||||
|
||||
it('should handle multiple simultaneous session refreshes with different scopes', async () => {
|
||||
const createSession = jest.fn();
|
||||
const refreshSession = jest.fn(async (scopes?: Set<string>) => ({
|
||||
scopes: scopes ?? new Set(),
|
||||
expired: false,
|
||||
}));
|
||||
const refreshSession = jest.fn(
|
||||
async (options?: AuthConnectorRefreshSessionOptions) => ({
|
||||
scopes: options?.scopes ?? new Set(),
|
||||
expired: false,
|
||||
}),
|
||||
);
|
||||
const manager = new RefreshingAuthSessionManager({
|
||||
connector: { createSession, refreshSession },
|
||||
...defaultOptions,
|
||||
|
||||
@@ -137,9 +137,9 @@ export class RefreshingAuthSessionManager<T> implements SessionManager<T> {
|
||||
return this.refreshPromise;
|
||||
}
|
||||
|
||||
this.refreshPromise = this.connector.refreshSession(
|
||||
this.helper.getExtendedScope(this.currentSession, scopes),
|
||||
);
|
||||
this.refreshPromise = this.connector.refreshSession({
|
||||
scopes: this.helper.getExtendedScope(this.currentSession, scopes),
|
||||
});
|
||||
|
||||
try {
|
||||
const session = await this.refreshPromise;
|
||||
|
||||
@@ -14,14 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { showLoginPopup } from './loginPopup';
|
||||
import { openLoginPopup } from './loginPopup';
|
||||
|
||||
describe('showLoginPopup', () => {
|
||||
describe('openLoginPopup', () => {
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should show an auth popup', async () => {
|
||||
it('should open an auth popup', async () => {
|
||||
const popupMock = { closed: false };
|
||||
const openSpy = jest
|
||||
.spyOn(window, 'open')
|
||||
@@ -29,15 +29,14 @@ describe('showLoginPopup', () => {
|
||||
const addEventListenerSpy = jest.spyOn(window, 'addEventListener');
|
||||
const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener');
|
||||
|
||||
const payloadPromise = showLoginPopup({
|
||||
url: 'my-origin/api/backend/auth/start?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fa%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fb',
|
||||
const payloadPromise = openLoginPopup({
|
||||
url: 'http://my-origin/api/backend/auth/start?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fa%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fb',
|
||||
name: 'test-popup',
|
||||
origin: 'my-origin',
|
||||
});
|
||||
|
||||
expect(openSpy).toHaveBeenCalledTimes(1);
|
||||
expect(openSpy.mock.calls[0][0]).toBe(
|
||||
'my-origin/api/backend/auth/start?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fa%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fb',
|
||||
'http://my-origin/api/backend/auth/start?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fa%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fb',
|
||||
);
|
||||
expect(openSpy.mock.calls[0][1]).toBe('test-popup');
|
||||
expect(addEventListenerSpy).toHaveBeenCalledTimes(1);
|
||||
@@ -57,16 +56,16 @@ describe('showLoginPopup', () => {
|
||||
|
||||
// None of these should be accepted
|
||||
listener({ source: popupMock } as MessageEvent);
|
||||
listener({ origin: 'my-origin' } as MessageEvent);
|
||||
listener({ origin: 'http://my-origin' } as MessageEvent);
|
||||
listener({ data: { type: 'authorization_response' } } as MessageEvent);
|
||||
listener({
|
||||
source: popupMock,
|
||||
origin: 'my-origin',
|
||||
origin: 'http://my-origin',
|
||||
data: {},
|
||||
} as MessageEvent);
|
||||
listener({
|
||||
source: popupMock,
|
||||
origin: 'my-origin',
|
||||
origin: 'http://my-origin',
|
||||
data: { type: 'not-auth-result', response: {} },
|
||||
} as MessageEvent);
|
||||
|
||||
@@ -79,7 +78,7 @@ describe('showLoginPopup', () => {
|
||||
// This should be accepted as a valid sessions response
|
||||
listener({
|
||||
source: popupMock,
|
||||
origin: 'my-origin',
|
||||
origin: 'http://my-origin',
|
||||
data: {
|
||||
type: 'authorization_response',
|
||||
response: myResponse,
|
||||
@@ -101,10 +100,9 @@ describe('showLoginPopup', () => {
|
||||
const addEventListenerSpy = jest.spyOn(window, 'addEventListener');
|
||||
const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener');
|
||||
|
||||
const payloadPromise = showLoginPopup({
|
||||
url: 'url',
|
||||
const payloadPromise = openLoginPopup({
|
||||
url: 'http://my-origin',
|
||||
name: 'name',
|
||||
origin: 'my-origin',
|
||||
});
|
||||
|
||||
expect(openSpy).toHaveBeenCalledTimes(1);
|
||||
@@ -115,7 +113,7 @@ describe('showLoginPopup', () => {
|
||||
|
||||
listener({
|
||||
source: popupMock,
|
||||
origin: 'my-origin',
|
||||
origin: 'http://my-origin',
|
||||
data: {
|
||||
type: 'authorization_response',
|
||||
error: {
|
||||
@@ -145,10 +143,9 @@ describe('showLoginPopup', () => {
|
||||
|
||||
openSpy.mockReturnValue(popupMock as Window);
|
||||
|
||||
const payloadPromise = showLoginPopup({
|
||||
url: 'url',
|
||||
const payloadPromise = openLoginPopup({
|
||||
url: 'http://origin',
|
||||
name: 'name',
|
||||
origin: 'origin',
|
||||
});
|
||||
|
||||
expect(openSpy).toHaveBeenCalledTimes(1);
|
||||
@@ -158,7 +155,7 @@ describe('showLoginPopup', () => {
|
||||
const listener = addEventListenerSpy.mock.calls[0][1] as EventListener;
|
||||
listener({
|
||||
source: popupMock,
|
||||
origin: 'origin',
|
||||
origin: 'http://origin',
|
||||
data: {
|
||||
type: 'config_info',
|
||||
targetOrigin: 'http://localhost',
|
||||
@@ -187,16 +184,15 @@ describe('showLoginPopup', () => {
|
||||
|
||||
openSpy.mockReturnValue(popupMock as Window);
|
||||
|
||||
const payloadPromise = showLoginPopup({
|
||||
url: 'url',
|
||||
const payloadPromise = openLoginPopup({
|
||||
url: 'http://origin',
|
||||
name: 'name',
|
||||
origin: 'origin',
|
||||
});
|
||||
|
||||
const listener = addEventListenerSpy.mock.calls[0][1] as EventListener;
|
||||
listener({
|
||||
source: popupMock,
|
||||
origin: 'origin',
|
||||
origin: 'http://origin',
|
||||
data: {
|
||||
type: 'config_info',
|
||||
targetOrigin: 'http://differenthost',
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type LoginPopupOptions = {
|
||||
export type OpenLoginPopupOptions = {
|
||||
/**
|
||||
* The URL that the auth popup should point to
|
||||
*/
|
||||
@@ -30,11 +30,6 @@ export type LoginPopupOptions = {
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* The origin of the final popup page that will post a message to this window.
|
||||
*/
|
||||
origin: string;
|
||||
|
||||
/**
|
||||
* The width of the popup in pixels, defaults to 500
|
||||
*/
|
||||
@@ -70,13 +65,17 @@ type AuthResult =
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function showLoginPopup(options: LoginPopupOptions): Promise<any> {
|
||||
export function openLoginPopup(
|
||||
options: OpenLoginPopupOptions,
|
||||
): Promise<unknown> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const width = options.width || 500;
|
||||
const height = options.height || 700;
|
||||
const left = window.screen.width / 2 - width / 2;
|
||||
const top = window.screen.height / 2 - height / 2;
|
||||
|
||||
const origin = new URL(options.url).origin;
|
||||
|
||||
const popup = window.open(
|
||||
options.url,
|
||||
options.name,
|
||||
@@ -96,7 +95,7 @@ export function showLoginPopup(options: LoginPopupOptions): Promise<any> {
|
||||
if (event.source !== popup) {
|
||||
return;
|
||||
}
|
||||
if (event.origin !== options.origin) {
|
||||
if (event.origin !== origin) {
|
||||
return;
|
||||
}
|
||||
const { data } = event;
|
||||
|
||||
Reference in New Issue
Block a user