UNPKG

@brionmario-experimental/asgardeo-auth-spa

Version:

Asgardeo Auth SPA SDK to be used in Single-Page Applications.

648 lines 26.9 kB
/** * 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. */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { AUTHORIZATION_CODE, AsgardeoAuthClient, AsgardeoAuthException, AuthenticationUtils, ResponseMode, SESSION_STATE, STATE } from "@asgardeo/auth-js"; import { DISABLE_HTTP_HANDLER, ENABLE_HTTP_HANDLER, GET_AUTH_URL, GET_BASIC_USER_INFO, GET_CONFIG_DATA, GET_CRYPTO_HELPER, GET_DECODED_IDP_ID_TOKEN, GET_DECODED_ID_TOKEN, GET_ID_TOKEN, GET_OIDC_SERVICE_ENDPOINTS, GET_SIGN_OUT_URL, HTTP_REQUEST, HTTP_REQUEST_ALL, INIT, IS_AUTHENTICATED, REFRESH_ACCESS_TOKEN, REQUEST_ACCESS_TOKEN, REQUEST_CUSTOM_GRANT, REQUEST_FINISH, REQUEST_START, REQUEST_SUCCESS, REVOKE_ACCESS_TOKEN, SET_SESSION_STATE, SIGN_OUT, SILENT_SIGN_IN_STATE, START_AUTO_REFRESH_TOKEN, Storage, UPDATE_CONFIG } from "../constants"; import { SPAHelper, SessionManagementHelper } from "../helpers"; import { LocalStore, MemoryStore, SessionStore } from "../stores"; import { SPAUtils } from "../utils"; import { SPACryptoUtils } from "../utils/crypto-utils"; const initiateStore = (store) => { switch (store) { case Storage.LocalStorage: return new LocalStore(); case Storage.SessionStorage: return new SessionStore(); case Storage.BrowserMemory: return new MemoryStore(); default: return new SessionStore(); } }; export const WebWorkerClient = (instanceID, config, webWorker, getAuthHelper) => __awaiter(void 0, void 0, void 0, function* () { var _a; /** * HttpClient handlers */ let httpClientHandlers; /** * API request time out. */ const _requestTimeout = (_a = config === null || config === void 0 ? void 0 : config.requestTimeout) !== null && _a !== void 0 ? _a : 60000; let _isHttpHandlerEnabled = true; let _getSignOutURLFromSessionStorage = false; const _store = initiateStore(config.storage); const _cryptoUtils = new SPACryptoUtils(); const _authenticationClient = new AsgardeoAuthClient(); yield _authenticationClient.initialize(config, _store, _cryptoUtils, instanceID); const _spaHelper = new SPAHelper(_authenticationClient); const _sessionManagementHelper = yield SessionManagementHelper(() => __awaiter(void 0, void 0, void 0, function* () { const message = { type: SIGN_OUT }; try { const signOutURL = yield communicate(message); return signOutURL; } catch (_b) { return SPAUtils.getSignOutURL(config.clientID, instanceID); } }), config.storage, (sessionState) => setSessionState(sessionState)); const _authenticationHelper = getAuthHelper(_authenticationClient, _spaHelper); const worker = new webWorker(); const communicate = (message) => { const channel = new MessageChannel(); worker.postMessage(message, [channel.port2]); return new Promise((resolve, reject) => { const timer = setTimeout(() => { reject(new AsgardeoAuthException("SPA-WEB_WORKER_CLIENT-COM-TO01", "Operation timed out.", "No response was received from the web worker for " + _requestTimeout / 1000 + " since dispatching the request")); }, _requestTimeout); return (channel.port1.onmessage = ({ data }) => { clearTimeout(timer); channel.port1.close(); channel.port2.close(); if (data === null || data === void 0 ? void 0 : data.success) { const responseData = (data === null || data === void 0 ? void 0 : data.data) ? JSON.parse(data === null || data === void 0 ? void 0 : data.data) : null; if (data === null || data === void 0 ? void 0 : data.blob) { responseData.data = data === null || data === void 0 ? void 0 : data.blob; } resolve(responseData); } else { reject(data.error ? JSON.parse(data.error) : null); } }); }); }; /** * Allows using custom grant types. * * @param {CustomGrantRequestParams} requestParams Request Parameters. * * @returns {Promise<HttpResponse|boolean>} A promise that resolves with a boolean value or the request * response if the the `returnResponse` attribute in the `requestParams` object is set to `true`. */ const requestCustomGrant = (requestParams) => { const message = { data: requestParams, type: REQUEST_CUSTOM_GRANT }; return communicate(message) .then((response) => { if (requestParams.preventSignOutURLUpdate) { _getSignOutURLFromSessionStorage = true; } return Promise.resolve(response); }) .catch((error) => { return Promise.reject(error); }); }; /** * * Send the API request to the web worker and returns the response. * * @param {HttpRequestConfig} config The Http Request Config object * * @returns {Promise<HttpResponse>} A promise that resolves with the response data. */ const httpRequest = (config) => { /** * * Currently FormData is not supported to send to a web worker * * Below workaround will represent FormData object as a JSON. * This workaround will not be needed once FormData object is made cloneable * Reference: https://github.com/whatwg/xhr/issues/55 */ if ((config === null || config === void 0 ? void 0 : config.data) && (config === null || config === void 0 ? void 0 : config.data) instanceof FormData) { config.data = Object.assign(Object.assign({}, Object.fromEntries(config === null || config === void 0 ? void 0 : config.data.entries())), { formData: true }); } const message = { data: config, type: HTTP_REQUEST }; return communicate(message) .then((response) => { return Promise.resolve(response); }) .catch((error) => __awaiter(void 0, void 0, void 0, function* () { if (_isHttpHandlerEnabled) { if (typeof httpClientHandlers.requestErrorCallback === "function") { yield httpClientHandlers.requestErrorCallback(error); } if (typeof httpClientHandlers.requestFinishCallback === "function") { httpClientHandlers.requestFinishCallback(); } } return Promise.reject(error); })); }; /** * * Send multiple API requests to the web worker and returns the response. * Similar `axios.spread` in functionality. * * @param {HttpRequestConfig[]} configs - The Http Request Config object * * @returns {Promise<HttpResponse<T>[]>} A promise that resolves with the response data. */ const httpRequestAll = (configs) => { const message = { data: configs, type: HTTP_REQUEST_ALL }; return communicate(message) .then((response) => { return Promise.resolve(response); }) .catch((error) => __awaiter(void 0, void 0, void 0, function* () { if (_isHttpHandlerEnabled) { if (typeof httpClientHandlers.requestErrorCallback === "function") { yield httpClientHandlers.requestErrorCallback(error); } if (typeof httpClientHandlers.requestFinishCallback === "function") { httpClientHandlers.requestFinishCallback(); } } return Promise.reject(error); })); }; const enableHttpHandler = () => { const message = { type: ENABLE_HTTP_HANDLER }; return communicate(message) .then(() => { _isHttpHandlerEnabled = true; return Promise.resolve(true); }) .catch((error) => { return Promise.reject(error); }); }; const disableHttpHandler = () => { const message = { type: DISABLE_HTTP_HANDLER }; return communicate(message) .then(() => { _isHttpHandlerEnabled = false; return Promise.resolve(true); }) .catch((error) => { return Promise.reject(error); }); }; /** * Initializes the object with authentication parameters. * * @param {ConfigInterface} config The configuration object. * * @returns {Promise<boolean>} Promise that resolves when initialization is successful. * */ const initialize = () => { if (!httpClientHandlers) { httpClientHandlers = { requestErrorCallback: () => Promise.resolve(), requestFinishCallback: () => null, requestStartCallback: () => null, requestSuccessCallback: () => null }; } worker.onmessage = ({ data }) => { switch (data.type) { case REQUEST_FINISH: (httpClientHandlers === null || httpClientHandlers === void 0 ? void 0 : httpClientHandlers.requestFinishCallback) && (httpClientHandlers === null || httpClientHandlers === void 0 ? void 0 : httpClientHandlers.requestFinishCallback()); break; case REQUEST_START: (httpClientHandlers === null || httpClientHandlers === void 0 ? void 0 : httpClientHandlers.requestStartCallback) && (httpClientHandlers === null || httpClientHandlers === void 0 ? void 0 : httpClientHandlers.requestStartCallback()); break; case REQUEST_SUCCESS: (httpClientHandlers === null || httpClientHandlers === void 0 ? void 0 : httpClientHandlers.requestSuccessCallback) && (httpClientHandlers === null || httpClientHandlers === void 0 ? void 0 : httpClientHandlers.requestSuccessCallback(data.data ? JSON.parse(data.data) : null)); break; } }; const message = { data: config, type: INIT }; return communicate(message) .then(() => { return Promise.resolve(true); }) .catch((error) => { return Promise.reject(error); }); }; const setSessionState = (sessionState) => { const message = { data: sessionState, type: SET_SESSION_STATE }; return communicate(message); }; const startAutoRefreshToken = () => { const message = { type: START_AUTO_REFRESH_TOKEN }; return communicate(message); }; const checkSession = () => __awaiter(void 0, void 0, void 0, function* () { const oidcEndpoints = yield getOIDCServiceEndpoints(); const config = yield getConfigData(); _authenticationHelper.initializeSessionManger(config, oidcEndpoints, () => __awaiter(void 0, void 0, void 0, function* () { return (yield getBasicUserInfo()).sessionState; }), (params) => __awaiter(void 0, void 0, void 0, function* () { return (yield getAuthorizationURL(params)).authorizationURL; }), _sessionManagementHelper); }); const constructSilentSignInUrl = (additionalParams = {}) => __awaiter(void 0, void 0, void 0, function* () { var _c; const config = yield getConfigData(); const message = { data: Object.assign({ prompt: "none", state: SILENT_SIGN_IN_STATE }, additionalParams), type: GET_AUTH_URL }; const response = yield communicate(message); const pkceKey = AuthenticationUtils.extractPKCEKeyFromStateParam((_c = new URL(response.authorizationURL).searchParams.get(STATE)) !== null && _c !== void 0 ? _c : ""); response.pkce && config.enablePKCE && SPAUtils.setPKCE(pkceKey, response.pkce); const urlString = response.authorizationURL; // Replace form_post with query const urlObject = new URL(urlString); urlObject.searchParams.set("response_mode", "query"); const url = urlObject.toString(); return url; }); /** * This method checks if there is an active user session in the server by sending a prompt none request. * If the user is signed in, this method sends a token request. Returns false otherwise. * * @return {Promise<BasicUserInfo|boolean} Returns a Promise that resolves with the BasicUserInfo * if the user is signed in or with `false` if there is no active user session in the server. */ const trySignInSilently = (additionalParams, tokenRequestConfig) => __awaiter(void 0, void 0, void 0, function* () { return yield _authenticationHelper.trySignInSilently(constructSilentSignInUrl, requestAccessToken, _sessionManagementHelper, additionalParams, tokenRequestConfig); }); /** * Generates an authorization URL. * * @param {GetAuthURLConfig} params Authorization URL params. * @returns {Promise<string>} Authorization URL. */ const getAuthorizationURL = (params) => __awaiter(void 0, void 0, void 0, function* () { const config = yield getConfigData(); const message = { data: params, type: GET_AUTH_URL }; return communicate(message).then((response) => __awaiter(void 0, void 0, void 0, function* () { var _d; if (response.pkce && config.enablePKCE) { const pkceKey = AuthenticationUtils.extractPKCEKeyFromStateParam((_d = new URL(response.authorizationURL).searchParams.get(STATE)) !== null && _d !== void 0 ? _d : ""); SPAUtils.setPKCE(pkceKey, response.pkce); } return Promise.resolve(response); })); }); const requestAccessToken = (resolvedAuthorizationCode, resolvedSessionState, resolvedState, tokenRequestConfig) => __awaiter(void 0, void 0, void 0, function* () { const config = yield getConfigData(); const pkceKey = AuthenticationUtils.extractPKCEKeyFromStateParam(resolvedState); const message = { data: { code: resolvedAuthorizationCode, pkce: config.enablePKCE ? SPAUtils.getPKCE(pkceKey) : undefined, sessionState: resolvedSessionState, state: resolvedState, tokenRequestConfig }, type: REQUEST_ACCESS_TOKEN }; config.enablePKCE && SPAUtils.removePKCE(pkceKey); return communicate(message) .then((response) => { const message = { type: GET_SIGN_OUT_URL }; return communicate(message) .then((url) => { SPAUtils.setSignOutURL(url, config.clientID, instanceID); // Enable OIDC Sessions Management only if it is set to true in the config. if (config.enableOIDCSessionManagement) { checkSession(); } startAutoRefreshToken(); return Promise.resolve(response); }) .catch((error) => { return Promise.reject(error); }); }) .catch((error) => { return Promise.reject(error); }); }); const shouldStopAuthn = () => __awaiter(void 0, void 0, void 0, function* () { return yield _sessionManagementHelper.receivePromptNoneResponse((sessionState) => __awaiter(void 0, void 0, void 0, function* () { return setSessionState(sessionState); })); }); const tryRetrievingUserInfo = () => __awaiter(void 0, void 0, void 0, function* () { if (yield isAuthenticated()) { yield startAutoRefreshToken(); // Enable OIDC Sessions Management only if it is set to true in the config. if (config.enableOIDCSessionManagement) { checkSession(); } return getBasicUserInfo(); } }); /** * Initiates the authentication flow. * * @returns {Promise<UserInfo>} A promise that resolves when authentication is successful. */ const signIn = (params, authorizationCode, sessionState, state, tokenRequestConfig) => __awaiter(void 0, void 0, void 0, function* () { var _e, _f, _g; const basicUserInfo = yield _authenticationHelper.handleSignIn(shouldStopAuthn, checkSession, tryRetrievingUserInfo); if (basicUserInfo) { return basicUserInfo; } else { let resolvedAuthorizationCode; let resolvedSessionState; let resolvedState; if ((config === null || config === void 0 ? void 0 : config.responseMode) === ResponseMode.formPost && authorizationCode) { resolvedAuthorizationCode = authorizationCode; resolvedSessionState = sessionState !== null && sessionState !== void 0 ? sessionState : ""; resolvedState = state !== null && state !== void 0 ? state : ""; } else { resolvedAuthorizationCode = (_e = new URL(window.location.href).searchParams.get(AUTHORIZATION_CODE)) !== null && _e !== void 0 ? _e : ""; resolvedSessionState = (_f = new URL(window.location.href).searchParams.get(SESSION_STATE)) !== null && _f !== void 0 ? _f : ""; resolvedState = (_g = new URL(window.location.href).searchParams.get(STATE)) !== null && _g !== void 0 ? _g : ""; SPAUtils.removeAuthorizationCode(); } if (resolvedAuthorizationCode && resolvedState) { return requestAccessToken(resolvedAuthorizationCode, resolvedSessionState, resolvedState, tokenRequestConfig); } return getAuthorizationURL(params) .then((response) => __awaiter(void 0, void 0, void 0, function* () { location.href = response.authorizationURL; yield SPAUtils.waitTillPageRedirect(); return Promise.resolve({ allowedScopes: "", displayName: "", email: "", sessionState: "", sub: "", tenantDomain: "", username: "" }); })) .catch((error) => { return Promise.reject(error); }); } }); /** * Initiates the sign out flow. * * @returns {Promise<boolean>} A promise that resolves when sign out is completed. */ const signOut = () => { return new Promise((resolve, reject) => { if (!_getSignOutURLFromSessionStorage) { const message = { type: SIGN_OUT }; return communicate(message) .then((response) => __awaiter(void 0, void 0, void 0, function* () { window.location.href = response; yield SPAUtils.waitTillPageRedirect(); return resolve(true); })) .catch((error) => { return reject(error); }); } else { window.location.href = SPAUtils.getSignOutURL(config.clientID, instanceID); SPAUtils.waitTillPageRedirect().then(() => { return Promise.resolve(true); }); } }); }; /** * Revokes token. * * @returns {Promise<boolean>} A promise that resolves when revoking is completed. */ const revokeAccessToken = () => { const message = { type: REVOKE_ACCESS_TOKEN }; return communicate(message) .then((response) => { _sessionManagementHelper.reset(); return Promise.resolve(response); }) .catch((error) => { return Promise.reject(error); }); }; const getOIDCServiceEndpoints = () => { const message = { type: GET_OIDC_SERVICE_ENDPOINTS }; return communicate(message) .then((response) => { return Promise.resolve(response); }) .catch((error) => { return Promise.reject(error); }); }; const getConfigData = () => { const message = { type: GET_CONFIG_DATA }; return communicate(message) .then((response) => { return Promise.resolve(response); }) .catch((error) => { return Promise.reject(error); }); }; const getBasicUserInfo = () => { const message = { type: GET_BASIC_USER_INFO }; return communicate(message) .then((response) => { return Promise.resolve(response); }) .catch((error) => { return Promise.reject(error); }); }; const getDecodedIDToken = () => { const message = { type: GET_DECODED_ID_TOKEN }; return communicate(message) .then((response) => { return Promise.resolve(response); }) .catch((error) => { return Promise.reject(error); }); }; const getDecodedIDPIDToken = () => { const message = { type: GET_DECODED_IDP_ID_TOKEN }; return communicate(message) .then((response) => { return Promise.resolve(response); }) .catch((error) => { return Promise.reject(error); }); }; const getCryptoHelper = () => { const message = { type: GET_CRYPTO_HELPER }; return communicate(message) .then((response) => { return Promise.resolve(response); }) .catch((error) => { return Promise.reject(error); }); }; const getIDToken = () => { const message = { type: GET_ID_TOKEN }; return communicate(message) .then((response) => { return Promise.resolve(response); }) .catch((error) => { return Promise.reject(error); }); }; const isAuthenticated = () => { const message = { type: IS_AUTHENTICATED }; return communicate(message) .then((response) => { return Promise.resolve(response); }) .catch((error) => { return Promise.reject(error); }); }; const refreshAccessToken = () => { const message = { type: REFRESH_ACCESS_TOKEN }; return communicate(message); }; const setHttpRequestSuccessCallback = (callback) => { if (callback && typeof callback === "function") { httpClientHandlers.requestSuccessCallback = callback; } }; const setHttpRequestErrorCallback = (callback) => { if (callback && typeof callback === "function") { httpClientHandlers.requestErrorCallback = callback; } }; const setHttpRequestStartCallback = (callback) => { if (callback && typeof callback === "function") { httpClientHandlers.requestStartCallback = callback; } }; const setHttpRequestFinishCallback = (callback) => { if (callback && typeof callback === "function") { httpClientHandlers.requestFinishCallback = callback; } }; const updateConfig = (newConfig) => __awaiter(void 0, void 0, void 0, function* () { const existingConfig = yield getConfigData(); const isCheckSessionIframeDifferent = !(existingConfig && existingConfig.endpoints && existingConfig.endpoints.checkSessionIframe && newConfig && newConfig.endpoints && newConfig.endpoints.checkSessionIframe && existingConfig.endpoints.checkSessionIframe === newConfig.endpoints.checkSessionIframe); const config = Object.assign(Object.assign({}, existingConfig), newConfig); const message = { data: config, type: UPDATE_CONFIG }; yield communicate(message); // Re-initiates check session if the check session endpoint is updated. if (config.enableOIDCSessionManagement && isCheckSessionIframeDifferent) { _sessionManagementHelper.reset(); checkSession(); } }); return { disableHttpHandler, enableHttpHandler, getBasicUserInfo, getConfigData, getCryptoHelper, getDecodedIDPIDToken, getDecodedIDToken, getIDToken, getOIDCServiceEndpoints, httpRequest, httpRequestAll, initialize, isAuthenticated, refreshAccessToken, requestCustomGrant, revokeAccessToken, setHttpRequestErrorCallback, setHttpRequestFinishCallback, setHttpRequestStartCallback, setHttpRequestSuccessCallback, signIn, signOut, trySignInSilently, updateConfig }; }); //# sourceMappingURL=web-worker-client.js.map