@brionmario-experimental/asgardeo-auth-spa
Version:
Asgardeo Auth SPA SDK to be used in Single-Page Applications.
317 lines (257 loc) • 11.5 kB
text/typescript
/**
* Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { AsgardeoAuthClient, GetAuthURLConfig, SESSION_STATE } from "@asgardeo/auth-js";
import {
CHECK_SESSION_SIGNED_IN,
CHECK_SESSION_SIGNED_OUT,
INITIALIZED_SILENT_SIGN_IN,
OP_IFRAME,
PROMPT_NONE_IFRAME,
RP_IFRAME,
SET_SESSION_STATE_FROM_IFRAME,
SILENT_SIGN_IN_STATE,
STATE,
STATE_QUERY,
Storage
} from "../constants";
import { AuthorizationInfo, Message, SessionManagementHelperInterface } from "../models";
import { SPAUtils } from "../utils";
export const SessionManagementHelper = (() => {
let _clientID: string;
let _checkSessionEndpoint: string;
let _sessionState: () => Promise<string>;
let _interval: number;
let _redirectURL: string;
let _sessionRefreshInterval: number;
let _signOut: () => Promise<string>;
let _sessionRefreshIntervalTimeout: number;
let _checkSessionIntervalTimeout: number;
let _storage: Storage;
let _setSessionState: (sessionState: string) => void;
let _getAuthorizationURL: (params?: GetAuthURLConfig) => Promise<string>;
const initialize = (
clientID: string,
checkSessionEndpoint: string,
getSessionState: () => Promise<string>,
interval: number,
sessionRefreshInterval: number,
redirectURL: string,
getAuthorizationURL: (params?: GetAuthURLConfig) => Promise<string>
): void => {
_clientID = clientID;
_checkSessionEndpoint = checkSessionEndpoint;
_sessionState = getSessionState;
_interval = interval;
_redirectURL = redirectURL;
_sessionRefreshInterval = sessionRefreshInterval;
_getAuthorizationURL = getAuthorizationURL;
if (_interval > -1) {
initiateCheckSession();
}
if (_sessionRefreshInterval > -1) {
sessionRefreshInterval = setInterval(() => {
sendPromptNoneRequest();
}, _sessionRefreshInterval * 1000) as unknown as number;
}
};
const initiateCheckSession = async (): Promise<void> => {
if (!_checkSessionEndpoint || !_clientID || !_redirectURL) {
return;
}
const OP_IFRAME = "opIFrame";
async function checkSession(): Promise<void> {
const sessionState = await _sessionState();
if (Boolean(_clientID) && Boolean(sessionState)) {
const message = `${ _clientID } ${ sessionState }`;
const rpIFrame = document.getElementById(RP_IFRAME) as HTMLIFrameElement;
const opIframe: HTMLIFrameElement
= rpIFrame?.contentDocument?.getElementById(OP_IFRAME) as HTMLIFrameElement;
const win: Window | null = opIframe.contentWindow;
win?.postMessage(message, _checkSessionEndpoint);
}
}
const rpIFrame = document.getElementById(RP_IFRAME) as HTMLIFrameElement;
const opIframe: HTMLIFrameElement
= rpIFrame?.contentDocument?.getElementById(OP_IFRAME) as HTMLIFrameElement;
opIframe.src = _checkSessionEndpoint + "?client_id=" + _clientID + "&redirect_uri=" + _redirectURL;
_checkSessionIntervalTimeout = setInterval(checkSession, _interval * 1000) as unknown as number;
listenToResponseFromOPIFrame();
};
/**
* Destroys session intervals.
*/
const reset = (): void => {
clearInterval(_checkSessionIntervalTimeout);
clearInterval(_sessionRefreshIntervalTimeout);
}
const listenToResponseFromOPIFrame = (): void => {
async function receiveMessage(e: MessageEvent) {
const targetOrigin = _checkSessionEndpoint;
if (!targetOrigin
|| targetOrigin?.indexOf(e.origin) < 0
|| e?.data?.type === SET_SESSION_STATE_FROM_IFRAME) {
return;
}
if (e.data === "unchanged") {
// [RP] session state has not changed
} else if (e.data === "error") {
window.location.href = await _signOut();
} else if (e.data === "changed") {
// [RP] session state has changed. Sending prompt=none request...
sendPromptNoneRequest();
}
}
window?.addEventListener("message", receiveMessage, false);
};
const sendPromptNoneRequest = async () => {
const rpIFrame = document.getElementById(RP_IFRAME) as HTMLIFrameElement;
const promptNoneIFrame: HTMLIFrameElement = rpIFrame?.contentDocument?.getElementById(
PROMPT_NONE_IFRAME
) as HTMLIFrameElement;
if (SPAUtils.canSendPromptNoneRequest()) {
SPAUtils.setPromptNoneRequestSent(true);
const receiveMessageListener = (e: MessageEvent<Message<string>>) => {
if (e?.data?.type === SET_SESSION_STATE_FROM_IFRAME) {
_setSessionState(e?.data?.data ?? "");
window?.removeEventListener("message", receiveMessageListener);
}
};
if (_storage === Storage.BrowserMemory || _storage === Storage.WebWorker) {
window?.addEventListener("message", receiveMessageListener);
}
const promptNoneURL: string = await _getAuthorizationURL({
prompt: "none",
response_mode: "query",
state: STATE
});
promptNoneIFrame.src = promptNoneURL;
}
};
/**
* This contains the logic to process the response of a prompt none request.
*
* @param setSessionState The method that sets the session state.
* on the output of the content of the redirect URL
*/
const receivePromptNoneResponse = async (
setSessionState?: (sessionState: string | null) => Promise<void>
): Promise<boolean> => {
const state = new URL(window.location.href).searchParams.get(STATE_QUERY);
const sessionState = new URL(window.location.href).searchParams.get(SESSION_STATE);
const parent = window.parent.parent;
if (state !== null && (state.includes(STATE) || state.includes(SILENT_SIGN_IN_STATE))) {
// Prompt none response.
const code = new URL(window.location.href).searchParams.get("code");
if (code !== null && code.length !== 0) {
if (state.includes(SILENT_SIGN_IN_STATE)) {
const message: Message<AuthorizationInfo> = {
data: {
code,
sessionState: sessionState ?? "",
state
},
type: CHECK_SESSION_SIGNED_IN
};
sessionStorage.setItem(INITIALIZED_SILENT_SIGN_IN, "false");
parent.postMessage(message, parent.origin);
SPAUtils.setPromptNoneRequestSent(false);
window.location.href = "about:blank";
await SPAUtils.waitTillPageRedirect();
return true;
}
const newSessionState = new URL(window.location.href).searchParams.get("session_state");
if (_storage === Storage.LocalStorage || _storage === Storage.SessionStorage) {
setSessionState && (await setSessionState(newSessionState));
} else {
const message: Message<string> = {
data: newSessionState ?? "",
type: SET_SESSION_STATE_FROM_IFRAME
};
window?.parent?.parent?.postMessage(message);
}
SPAUtils.setPromptNoneRequestSent(false);
window.location.href = "about:blank";
await SPAUtils.waitTillPageRedirect();
return true;
} else {
if (state.includes(SILENT_SIGN_IN_STATE)) {
const message: Message<null> = {
type: CHECK_SESSION_SIGNED_OUT
};
window.parent.parent.postMessage(message, parent.origin);
SPAUtils.setPromptNoneRequestSent(false);
window.location.href = "about:blank";
await SPAUtils.waitTillPageRedirect();
return true;
}
SPAUtils.setPromptNoneRequestSent(false);
const signOutURL = await _signOut();
// Clearing user session data before redirecting to the signOutURL because user has been already logged
// out by the initial logout request in the single logout flow.
await AsgardeoAuthClient.clearUserSessionData();
parent.location.href = signOutURL;
window.location.href = "about:blank";
await SPAUtils.waitTillPageRedirect();
return true;
}
}
return false;
};
return async (
signOut: () => Promise<string>,
storage: Storage,
setSessionState: (sessionState: string) => void
): Promise<SessionManagementHelperInterface> => {
let rpIFrame = document.createElement("iframe");
rpIFrame.setAttribute("id", RP_IFRAME);
rpIFrame.style.display = "none";
let rpIframeLoaded: boolean = false;
rpIFrame.onload = () => {
rpIFrame = document.getElementById(RP_IFRAME) as HTMLIFrameElement;
const rpDoc = rpIFrame?.contentDocument;
const opIFrame = rpDoc?.createElement("iframe");
if (opIFrame) {
opIFrame.setAttribute("id", OP_IFRAME);
opIFrame.style.display = "none";
}
const promptNoneIFrame = rpDoc?.createElement("iframe");
if (promptNoneIFrame) {
promptNoneIFrame.setAttribute("id", PROMPT_NONE_IFRAME);
promptNoneIFrame.style.display = "none";
}
opIFrame && rpIFrame?.contentDocument?.body?.appendChild(opIFrame);
promptNoneIFrame && rpIFrame?.contentDocument?.body?.appendChild(promptNoneIFrame);
rpIframeLoaded = true;
}
document?.body?.appendChild(rpIFrame);
_signOut = signOut;
_storage = storage;
_setSessionState = setSessionState;
const sleep = (): Promise<any> => {
return new Promise((resolve) => setTimeout(resolve, 1));
};
while (rpIframeLoaded === false) {
await sleep();
}
return {
initialize,
receivePromptNoneResponse,
reset
};
};
})();