@brionmario-experimental/asgardeo-auth-spa
Version:
Asgardeo Auth SPA SDK to be used in Single-Page Applications.
648 lines • 26.9 kB
JavaScript
/**
* 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