Skip to content

Commit

Permalink
refactor: remove iframe session-renewal support (#2152)
Browse files Browse the repository at this point in the history
* style: fix changelog formatting

* refactor: remove support for iframe-based session renewal
  • Loading branch information
ThisIsMissEm authored May 31, 2022
1 parent d679109 commit 8e38f2a
Show file tree
Hide file tree
Showing 11 changed files with 118 additions and 673 deletions.
234 changes: 118 additions & 116 deletions CHANGELOG.md

Large diffs are not rendered by default.

181 changes: 0 additions & 181 deletions packages/browser/src/Session.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,6 @@ const mockLocation = (mockedLocation: string) => {
window.history.replaceState = jest.fn();
};

jest.mock("../src/iframe");

describe("Session", () => {
describe("constructor", () => {
it("accepts an empty config", async () => {
Expand Down Expand Up @@ -394,22 +392,6 @@ describe("Session", () => {
);
expect(incomingRedirectHandler).toHaveBeenCalled();
});

it("posts the redirect IRI to the parent if in an iframe", async () => {
// Pretend we are in an iframe.
const frameElement = jest.spyOn(window, "frameElement", "get");
frameElement.mockReturnValueOnce({} as Element);

const mySession = new Session({}, "mySession");
const iframe = jest.requireMock("../src/iframe");
const postIri = jest.spyOn(iframe as any, "postRedirectUrlToParent");
await mySession.handleIncomingRedirect({
url: "https://some.redirect.url?code=someCode&state=someState",
});
expect(postIri).toHaveBeenCalledWith(
"https://some.redirect.url?code=someCode&state=someState"
);
});
});

describe("onError", () => {
Expand Down Expand Up @@ -587,7 +569,6 @@ describe("Session", () => {
clientId: "some client ID",
clientSecret: "some client secret",
redirectUrl: "https://some.redirect/url",
inIframe: false,
},
expect.anything()
);
Expand Down Expand Up @@ -897,168 +878,6 @@ describe("Session", () => {
});
});

describe("on tokenRenewal signal", () => {
it("triggers silent authentication in an iframe when receiving the signal", async () => {
const sessionId = "mySession";
mockLocalStorage({
[KEY_CURRENT_SESSION]: sessionId,
});
mockLocation("https://mock.current/location");
const mockedStorage = new StorageUtility(
mockStorage({
[`${USER_SESSION_PREFIX}:${sessionId}`]: {
isLoggedIn: "true",
},
}),
mockStorage({})
);
const clientAuthentication = mockClientAuthentication({
sessionInfoManager: mockSessionInfoManager(mockedStorage),
});
const validateCurrentSessionPromise = Promise.resolve({
issuer: "https://some.issuer",
clientAppId: "some client ID",
clientAppSecret: "some client secret",
redirectUrl: "https://some.redirect/url",
tokenType: "DPoP",
});
clientAuthentication.validateCurrentSession = jest
.fn()
.mockReturnValue(
validateCurrentSessionPromise
) as typeof clientAuthentication.validateCurrentSession;
const incomingRedirectPromise = Promise.resolve();
clientAuthentication.handleIncomingRedirect = jest
.fn()
.mockReturnValueOnce(
incomingRedirectPromise
) as typeof clientAuthentication.handleIncomingRedirect;
clientAuthentication.login = jest.fn();
const mySession = new Session({ clientAuthentication }, sessionId);
// Send the signal to the session
mySession.emit("tokenRenewal");
await incomingRedirectPromise;
await validateCurrentSessionPromise;

expect(clientAuthentication.login).toHaveBeenCalledWith(
{
sessionId: "mySession",
tokenType: "DPoP",
oidcIssuer: "https://some.issuer",
prompt: "none",
clientId: "some client ID",
clientSecret: "some client secret",
redirectUrl: "https://some.redirect/url",
inIframe: true,
},
expect.anything()
);
// Check that second parameter is of type session
expect(
(clientAuthentication.login as any).mock.calls[0][1]
).toBeInstanceOf(Session);
});

it("sets the updated session info after silently refreshing", async () => {
const clientAuthentication = mockClientAuthentication();
const incomingRedirectPromise = Promise.resolve({
isLoggedIn: true,
webId: "https://some.pod/profile#me",
sessionId: "someSessionId",
expirationDate: 961106400,
});
clientAuthentication.handleIncomingRedirect = jest
.fn()
.mockReturnValueOnce(
incomingRedirectPromise
) as typeof clientAuthentication.handleIncomingRedirect;

const windowAddEventListener = jest.spyOn(window, "addEventListener");
// ../src/iframe is mocked for other tests,
// but we need `setupIframeListener` to actually be executed
// so that the callback gets called:
const iframeMock = jest.requireMock("../src/iframe") as any;
const iframeActual = jest.requireActual("../src/iframe") as any;
iframeMock.setupIframeListener.mockImplementationOnce(
iframeActual.setupIframeListener
);
const mySession = new Session({ clientAuthentication });

// `window.addEventListener` gets called once with ("message", handler)
// — get that handler.
const messageEventHandler = windowAddEventListener.mock
.calls[0][1] as EventListener;
const mockedEvent = {
origin: window.location.origin,
source: null,
data: {
redirectUrl: "http://arbitrary.com",
},
} as MessageEvent;
// This handler will call `clientAuthentication.handleIncomingRedirect`,
// which will return our sessionInfo values:
messageEventHandler(mockedEvent);

await incomingRedirectPromise;
expect(mySession.info.webId).toBe("https://some.pod/profile#me");
expect(mySession.info.sessionId).toBe("someSessionId");
expect(mySession.info.expirationDate).toBe(961106400);
});

it("does not change the existing session if silent authentication failed", async () => {
const clientAuthentication = mockClientAuthentication();
const incomingRedirectPromise = Promise.resolve({
isLoggedIn: false,
});
clientAuthentication.handleIncomingRedirect = jest
.fn()
.mockReturnValueOnce(
incomingRedirectPromise
) as typeof clientAuthentication.handleIncomingRedirect;

const windowAddEventListener = jest.spyOn(window, "addEventListener");
// ../src/iframe is mocked for other tests,
// but we need `setupIframeListener` to actually be executed
// so that the callback gets called:
const iframeMock = jest.requireMock("../src/iframe") as any;
const iframeActual = jest.requireActual("../src/iframe") as any;
iframeMock.setupIframeListener.mockImplementationOnce(
iframeActual.setupIframeListener
);
const mySession = new Session({ clientAuthentication });
// The `any` assertion is necessary because Session.info is not meant to
// be written to; we only do so for tests to pretend we have an existing
// logged-in session that remains logged in after failed silent
// authentication.
(mySession as any).info = {
isLoggedIn: true,
webId: "https://some.pod/profile#me",
sessionId: "someSessionId",
expirationDate: 961106400,
};

// `window.addEventListener` gets called once with ("message", handler)
// — get that handler.
const messageEventHandler = windowAddEventListener.mock
.calls[0][1] as EventListener;
const mockedEvent = {
origin: window.location.origin,
source: null,
data: {
redirectUrl: "http://arbitrary.com",
},
} as MessageEvent;
// This handler will call `clientAuthentication.handleIncomingRedirect`,
// which will return our sessionInfo values:
messageEventHandler(mockedEvent);

await incomingRedirectPromise;
expect(mySession.info.webId).toBe("https://some.pod/profile#me");
expect(mySession.info.sessionId).toBe("someSessionId");
expect(mySession.info.expirationDate).toBe(961106400);
});
});

describe("onSessionExpiration", () => {
it("calls the provided callback when receiving the appropriate event", async () => {
const myCallback = jest.fn();
Expand Down
48 changes: 0 additions & 48 deletions packages/browser/src/Session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import { v4 } from "uuid";
import ClientAuthentication from "./ClientAuthentication";
import { getClientAuthenticationWithDependencies } from "./dependencies";
import { KEY_CURRENT_SESSION, KEY_CURRENT_URL } from "./constant";
import { postRedirectUrlToParent, setupIframeListener } from "./iframe";

export interface ISessionOptions {
/**
Expand Down Expand Up @@ -98,11 +97,6 @@ export interface IHandleIncomingRedirectOptions {
export async function silentlyAuthenticate(
sessionId: string,
clientAuthn: ClientAuthentication,
options: {
inIframe?: boolean;
} = {
inIframe: false,
},
session: Session
): Promise<boolean> {
const storedSessionInfo = await clientAuthn.validateCurrentSession(sessionId);
Expand All @@ -121,7 +115,6 @@ export async function silentlyAuthenticate(
clientId: storedSessionInfo.clientAppId,
clientSecret: storedSessionInfo.clientAppSecret,
tokenType: storedSessionInfo.tokenType ?? "DPoP",
inIframe: options.inIframe,
},
session
);
Expand Down Expand Up @@ -194,36 +187,6 @@ export class Session extends EventEmitter {
};
}

// Listen for messages from children iframes.
setupIframeListener(async (redirectUrl: string) => {
const sessionInfo =
await this.clientAuthentication.handleIncomingRedirect(
redirectUrl,
this
);

// If silent authentication was not successful, do nothing;
// the existing session might still be valid for a while,
// and will expire by itself.
if (!isLoggedIn(sessionInfo)) {
return;
}
// After having revalidated the session,
// make sure to apply the new expiration time:
this.setSessionInfo(sessionInfo);
});
// Listen for the 'tokenRenewal' signal to trigger the silent token renewal.
this.on("tokenRenewal", () =>
silentlyAuthenticate(
this.info.sessionId,
this.clientAuthentication,
{
inIframe: true,
},
this
)
);

// When a session is logged in, we want to track its ID in local storage to
// enable silent refresh. The current session ID specifically stored in 'localStorage'
// (as opposed to using our storage abstraction layer) because it is only
Expand Down Expand Up @@ -321,13 +284,6 @@ export class Session extends EventEmitter {
typeof inputOptions === "string" ? { url: inputOptions } : inputOptions;
const url = options.url ?? window.location.href;

if (window.frameElement !== null) {
// This is being loaded from an iframe, so send the redirect
// URL to the parent window on the same origin.
postRedirectUrlToParent(url);
return undefined;
}

this.tokenRequestInProgress = true;
const sessionInfo = await this.clientAuthentication.handleIncomingRedirect(
url,
Expand Down Expand Up @@ -357,13 +313,9 @@ export class Session extends EventEmitter {
// ...if not, then there is no ID token, and so silent authentication cannot happen, but
// if we do have a stored session ID, attempt to re-authenticate now silently.
if (storedSessionId !== null) {
// TODO: iframe-based authentication being still experimental, it is disabled
// by default here. When it settles down, the following could be set to true,
// in which case the unresolving promise afterwards would need to be changed.
const attemptedSilentAuthentication = await silentlyAuthenticate(
storedSessionId,
this.clientAuthentication,
undefined,
this
);
// At this point, we know that the main window will imminently be redirected.
Expand Down
Loading

0 comments on commit 8e38f2a

Please sign in to comment.