@brionmario-experimental/asgardeo-auth-spa
Version:
Asgardeo Auth SPA SDK to be used in Single-Page Applications.
539 lines • 29.2 kB
JavaScript
/**
* Copyright (c) 2022-2024, 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 { AsgardeoAuthException, AuthenticationUtils } from "@asgardeo/auth-js";
import { ACCESS_TOKEN_INVALID, CHECK_SESSION_SIGNED_IN, CHECK_SESSION_SIGNED_OUT, CUSTOM_GRANT_CONFIG, ERROR, ERROR_DESCRIPTION, PROMPT_NONE_IFRAME, REFRESH_ACCESS_TOKEN_ERR0R, RP_IFRAME, Storage } from "../constants";
import { SPAUtils } from "../utils";
export class AuthenticationHelper {
constructor(authClient, spaHelper) {
this._authenticationClient = authClient;
this._dataLayer = this._authenticationClient.getDataLayer();
this._spaHelper = spaHelper;
this._instanceID = this._authenticationClient.getInstanceID();
this._isTokenRefreshing = false;
}
enableHttpHandler(httpClient) {
(httpClient === null || httpClient === void 0 ? void 0 : httpClient.enableHandler) && httpClient.enableHandler();
}
disableHttpHandler(httpClient) {
(httpClient === null || httpClient === void 0 ? void 0 : httpClient.disableHandler) && httpClient.disableHandler();
}
initializeSessionManger(config, oidcEndpoints, getSessionState, getAuthzURL, sessionManagementHelper) {
var _a, _b, _c;
sessionManagementHelper.initialize(config.clientID, (_a = oidcEndpoints.checkSessionIframe) !== null && _a !== void 0 ? _a : "", getSessionState, (_b = config.checkSessionInterval) !== null && _b !== void 0 ? _b : 3, (_c = config.sessionRefreshInterval) !== null && _c !== void 0 ? _c : 300, config.signInRedirectURL, getAuthzURL);
}
requestCustomGrant(config, enableRetrievingSignOutURLFromSession) {
var _a, _b, _c;
return __awaiter(this, void 0, void 0, function* () {
let useDefaultEndpoint = true;
let matches = false;
// If the config does not contains a token endpoint, default token endpoint will be used.
if (config === null || config === void 0 ? void 0 : config.tokenEndpoint) {
useDefaultEndpoint = false;
for (const baseUrl of [
...((_b = (_a = (yield this._dataLayer.getConfigData())) === null || _a === void 0 ? void 0 : _a.resourceServerURLs) !== null && _b !== void 0 ? _b : []),
config.baseUrl
]) {
if (baseUrl && ((_c = config.tokenEndpoint) === null || _c === void 0 ? void 0 : _c.startsWith(baseUrl))) {
matches = true;
break;
}
}
}
if (config.shouldReplayAfterRefresh) {
this._dataLayer.setTemporaryDataParameter(CUSTOM_GRANT_CONFIG, JSON.stringify(config));
}
if (useDefaultEndpoint || matches) {
return this._authenticationClient
.requestCustomGrant(config)
.then((response) => __awaiter(this, void 0, void 0, function* () {
if (enableRetrievingSignOutURLFromSession &&
typeof enableRetrievingSignOutURLFromSession === "function") {
enableRetrievingSignOutURLFromSession(config);
}
if (config.returnsSession) {
this._spaHelper.refreshAccessTokenAutomatically(this);
return this._authenticationClient.getBasicUserInfo();
}
else {
return response;
}
}))
.catch((error) => {
return Promise.reject(error);
});
}
else {
return Promise.reject(new AsgardeoAuthException("SPA-MAIN_THREAD_CLIENT-RCG-IV01", "Request to the provided endpoint is prohibited.", "Requests can only be sent to resource servers specified by the `resourceServerURLs`" +
" attribute while initializing the SDK. The specified token endpoint in this request " +
"cannot be found among the `resourceServerURLs`"));
}
});
}
getCustomGrantConfigData() {
return __awaiter(this, void 0, void 0, function* () {
const configString = yield this._dataLayer.getTemporaryDataParameter(CUSTOM_GRANT_CONFIG);
if (configString) {
return JSON.parse(configString);
}
else {
return null;
}
});
}
refreshAccessToken(enableRetrievingSignOutURLFromSession) {
return __awaiter(this, void 0, void 0, function* () {
try {
yield this._authenticationClient.refreshAccessToken();
const customGrantConfig = yield this.getCustomGrantConfigData();
if (customGrantConfig) {
yield this.requestCustomGrant(customGrantConfig, enableRetrievingSignOutURLFromSession);
}
this._spaHelper.refreshAccessTokenAutomatically(this);
return this._authenticationClient.getBasicUserInfo();
}
catch (error) {
const refreshTokenError = {
type: REFRESH_ACCESS_TOKEN_ERR0R
};
window.postMessage(refreshTokenError);
return Promise.reject(error);
}
});
}
retryFailedRequests(failedRequest) {
return __awaiter(this, void 0, void 0, function* () {
const httpClient = failedRequest.httpClient;
const requestConfig = failedRequest.requestConfig;
const isHttpHandlerEnabled = failedRequest.isHttpHandlerEnabled;
const httpErrorCallback = failedRequest.httpErrorCallback;
const httpFinishCallback = failedRequest.httpFinishCallback;
// Wait until the token is refreshed.
yield SPAUtils.until(() => !this._isTokenRefreshing);
try {
const httpResponse = yield httpClient.request(requestConfig);
return Promise.resolve(httpResponse);
}
catch (error) {
if (isHttpHandlerEnabled) {
if (typeof httpErrorCallback === "function") {
yield httpErrorCallback(error);
}
if (typeof httpFinishCallback === "function") {
httpFinishCallback();
}
}
return Promise.reject(error);
}
});
}
httpRequest(httpClient, requestConfig, isHttpHandlerEnabled, httpErrorCallback, httpFinishCallback, enableRetrievingSignOutURLFromSession) {
var _a, _b;
return __awaiter(this, void 0, void 0, function* () {
let matches = false;
const config = yield this._dataLayer.getConfigData();
for (const baseUrl of [
...((_a = (yield (config === null || config === void 0 ? void 0 : config.resourceServerURLs))) !== null && _a !== void 0 ? _a : []),
config.baseUrl
]) {
if (baseUrl && ((_b = requestConfig === null || requestConfig === void 0 ? void 0 : requestConfig.url) === null || _b === void 0 ? void 0 : _b.startsWith(baseUrl))) {
matches = true;
break;
}
}
if (matches) {
return httpClient
.request(requestConfig)
.then((response) => {
return Promise.resolve(response);
})
.catch((error) => __awaiter(this, void 0, void 0, function* () {
var _c, _d, _e;
if (((_c = error === null || error === void 0 ? void 0 : error.response) === null || _c === void 0 ? void 0 : _c.status) === 401 || !(error === null || error === void 0 ? void 0 : error.response)) {
if (this._isTokenRefreshing) {
return this.retryFailedRequests({
enableRetrievingSignOutURLFromSession,
httpClient,
httpErrorCallback,
httpFinishCallback,
isHttpHandlerEnabled,
requestConfig
});
}
this._isTokenRefreshing = true;
// Try to refresh the token
let refreshAccessTokenResponse;
try {
refreshAccessTokenResponse = yield this.refreshAccessToken(enableRetrievingSignOutURLFromSession);
this._isTokenRefreshing = false;
}
catch (refreshError) {
this._isTokenRefreshing = false;
if (isHttpHandlerEnabled) {
if (typeof httpErrorCallback === "function") {
yield httpErrorCallback(Object.assign(Object.assign({}, error), { code: ACCESS_TOKEN_INVALID }));
}
if (typeof httpFinishCallback === "function") {
httpFinishCallback();
}
}
throw new AsgardeoAuthException("SPA-AUTH_HELPER-HR-SE01", (_d = refreshError === null || refreshError === void 0 ? void 0 : refreshError.name) !== null && _d !== void 0 ? _d : "Refresh token request failed.", (_e = refreshError === null || refreshError === void 0 ? void 0 : refreshError.message) !== null && _e !== void 0 ? _e : "An error occurred while trying to refresh the " +
"access token following a 401 response from the server.");
}
// Retry the request after refreshing the token
if (refreshAccessTokenResponse) {
try {
const httpResponse = yield httpClient.request(requestConfig);
return Promise.resolve(httpResponse);
}
catch (error) {
if (isHttpHandlerEnabled) {
if (typeof httpErrorCallback === "function") {
yield httpErrorCallback(error);
}
if (typeof httpFinishCallback === "function") {
httpFinishCallback();
}
}
return Promise.reject(error);
}
}
}
if (isHttpHandlerEnabled) {
if (typeof httpErrorCallback === "function") {
yield httpErrorCallback(error);
}
if (typeof httpFinishCallback === "function") {
httpFinishCallback();
}
}
return Promise.reject(error);
}));
}
else {
return Promise.reject(new AsgardeoAuthException("SPA-AUTH_HELPER-HR-IV02", "Request to the provided endpoint is prohibited.", "Requests can only be sent to resource servers specified by the `resourceServerURLs`" +
" attribute while initializing the SDK. The specified endpoint in this request " +
"cannot be found among the `resourceServerURLs`"));
}
});
}
httpRequestAll(requestConfigs, httpClient, isHttpHandlerEnabled, httpErrorCallback, httpFinishCallback) {
var _a, _b, _c;
return __awaiter(this, void 0, void 0, function* () {
let matches = true;
const config = yield this._dataLayer.getConfigData();
for (const requestConfig of requestConfigs) {
let urlMatches = false;
for (const baseUrl of [
...((_b = (_a = (yield config)) === null || _a === void 0 ? void 0 : _a.resourceServerURLs) !== null && _b !== void 0 ? _b : []),
config.baseUrl
]) {
if (baseUrl && ((_c = requestConfig.url) === null || _c === void 0 ? void 0 : _c.startsWith(baseUrl))) {
urlMatches = true;
break;
}
}
if (!urlMatches) {
matches = false;
break;
}
}
const requests = [];
if (matches) {
requestConfigs.forEach((request) => {
requests.push(httpClient.request(request));
});
return ((httpClient === null || httpClient === void 0 ? void 0 : httpClient.all) &&
httpClient
.all(requests)
.then((responses) => {
return Promise.resolve(responses);
})
.catch((error) => __awaiter(this, void 0, void 0, function* () {
var _d, _e, _f;
if (((_d = error === null || error === void 0 ? void 0 : error.response) === null || _d === void 0 ? void 0 : _d.status) === 401 || !(error === null || error === void 0 ? void 0 : error.response)) {
let refreshTokenResponse;
try {
refreshTokenResponse = yield this._authenticationClient.refreshAccessToken();
}
catch (refreshError) {
if (isHttpHandlerEnabled) {
if (typeof httpErrorCallback === "function") {
yield httpErrorCallback(Object.assign(Object.assign({}, error), { code: ACCESS_TOKEN_INVALID }));
}
if (typeof httpFinishCallback === "function") {
httpFinishCallback();
}
}
throw new AsgardeoAuthException("SPA-AUTH_HELPER-HRA-SE01", (_e = refreshError === null || refreshError === void 0 ? void 0 : refreshError.name) !== null && _e !== void 0 ? _e : "Refresh token request failed.", (_f = refreshError === null || refreshError === void 0 ? void 0 : refreshError.message) !== null && _f !== void 0 ? _f : "An error occurred while trying to refresh the " +
"access token following a 401 response from the server.");
}
if (refreshTokenResponse) {
return (httpClient.all &&
httpClient
.all(requests)
.then((response) => {
return Promise.resolve(response);
})
.catch((error) => __awaiter(this, void 0, void 0, function* () {
if (isHttpHandlerEnabled) {
if (typeof httpErrorCallback === "function") {
yield httpErrorCallback(error);
}
if (typeof httpFinishCallback === "function") {
httpFinishCallback();
}
}
return Promise.reject(error);
})));
}
}
if (isHttpHandlerEnabled) {
if (typeof httpErrorCallback === "function") {
yield httpErrorCallback(error);
}
if (typeof httpFinishCallback === "function") {
httpFinishCallback();
}
}
return Promise.reject(error);
})));
}
else {
throw new AsgardeoAuthException("SPA-AUTH_HELPER-HRA-IV02", "Request to the provided endpoint is prohibited.", "Requests can only be sent to resource servers specified by the `resourceServerURLs`" +
" attribute while initializing the SDK. The specified endpoint in this request " +
"cannot be found among the `resourceServerURLs`");
}
});
}
requestAccessToken(authorizationCode, sessionState, checkSession, pkce, state, tokenRequestConfig) {
return __awaiter(this, void 0, void 0, function* () {
const config = yield this._dataLayer.getConfigData();
if (config.storage === Storage.BrowserMemory && config.enablePKCE && sessionState) {
const pkce = SPAUtils.getPKCE(AuthenticationUtils.extractPKCEKeyFromStateParam(sessionState));
yield this._authenticationClient.setPKCECode(AuthenticationUtils.extractPKCEKeyFromStateParam(sessionState), pkce);
}
else if (config.storage === Storage.WebWorker && pkce) {
yield this._authenticationClient.setPKCECode(pkce, state !== null && state !== void 0 ? state : "");
}
if (authorizationCode) {
return this._authenticationClient
.requestAccessToken(authorizationCode, sessionState !== null && sessionState !== void 0 ? sessionState : "", state !== null && state !== void 0 ? state : "", undefined, tokenRequestConfig)
.then(() => __awaiter(this, void 0, void 0, function* () {
// Disable this temporarily
/* if (config.storage === Storage.BrowserMemory) {
SPAUtils.setSignOutURL(await _authenticationClient.getSignOutURL());
} */
if (config.storage !== Storage.WebWorker) {
SPAUtils.setSignOutURL(yield this._authenticationClient.getSignOutURL(), config.clientID, this._instanceID);
if (this._spaHelper) {
this._spaHelper.clearRefreshTokenTimeout();
this._spaHelper.refreshAccessTokenAutomatically(this);
}
// Enable OIDC Sessions Management only if it is set to true in the config.
if (checkSession &&
typeof checkSession === "function" &&
config.enableOIDCSessionManagement) {
checkSession();
}
}
else {
if (this._spaHelper) {
this._spaHelper.refreshAccessTokenAutomatically(this);
}
}
return this._authenticationClient.getBasicUserInfo();
}))
.catch((error) => {
return Promise.reject(error);
});
}
return Promise.reject(new AsgardeoAuthException("SPA-AUTH_HELPER-RAT1-NF01", "No authorization code.", "No authorization code was found."));
});
}
trySignInSilently(constructSilentSignInUrl, requestAccessToken, sessionManagementHelper, additionalParams, tokenRequestConfig) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
// This block is executed by the iFrame when the server redirects with the authorization code.
if (SPAUtils.isInitializedSilentSignIn()) {
yield sessionManagementHelper.receivePromptNoneResponse();
return Promise.resolve({
allowedScopes: "",
displayName: "",
email: "",
sessionState: "",
sub: "",
tenantDomain: "",
username: ""
});
}
// This gets executed in the main thread and sends the prompt none request.
const rpIFrame = document.getElementById(RP_IFRAME);
const promptNoneIFrame = (_a = rpIFrame === null || rpIFrame === void 0 ? void 0 : rpIFrame.contentDocument) === null || _a === void 0 ? void 0 : _a.getElementById(PROMPT_NONE_IFRAME);
try {
const url = yield constructSilentSignInUrl(additionalParams);
promptNoneIFrame.src = url;
}
catch (error) {
return Promise.reject(error);
}
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
resolve(false);
}, 10000);
const listenToPromptNoneIFrame = (e) => __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c, _d;
const data = e.data;
if ((data === null || data === void 0 ? void 0 : data.type) == CHECK_SESSION_SIGNED_OUT) {
window.removeEventListener("message", listenToPromptNoneIFrame);
clearTimeout(timer);
resolve(false);
}
if ((data === null || data === void 0 ? void 0 : data.type) == CHECK_SESSION_SIGNED_IN && ((_a = data === null || data === void 0 ? void 0 : data.data) === null || _a === void 0 ? void 0 : _a.code)) {
requestAccessToken((_b = data === null || data === void 0 ? void 0 : data.data) === null || _b === void 0 ? void 0 : _b.code, (_c = data === null || data === void 0 ? void 0 : data.data) === null || _c === void 0 ? void 0 : _c.sessionState, (_d = data === null || data === void 0 ? void 0 : data.data) === null || _d === void 0 ? void 0 : _d.state, tokenRequestConfig)
.then((response) => {
window.removeEventListener("message", listenToPromptNoneIFrame);
resolve(response);
})
.catch((error) => {
window.removeEventListener("message", listenToPromptNoneIFrame);
reject(error);
})
.finally(() => {
clearTimeout(timer);
});
}
});
window.addEventListener("message", listenToPromptNoneIFrame);
});
});
}
handleSignIn(shouldStopAuthn, checkSession, tryRetrievingUserInfo) {
return __awaiter(this, void 0, void 0, function* () {
const config = yield this._dataLayer.getConfigData();
if (yield shouldStopAuthn()) {
return Promise.resolve({
allowedScopes: "",
displayName: "",
email: "",
sessionState: "",
sub: "",
tenantDomain: "",
username: ""
});
}
if (config.storage !== Storage.WebWorker) {
if (yield this._authenticationClient.isAuthenticated()) {
this._spaHelper.clearRefreshTokenTimeout();
this._spaHelper.refreshAccessTokenAutomatically(this);
// Enable OIDC Sessions Management only if it is set to true in the config.
if (config.enableOIDCSessionManagement) {
checkSession();
}
return Promise.resolve(yield this._authenticationClient.getBasicUserInfo());
}
}
const error = new URL(window.location.href).searchParams.get(ERROR);
const errorDescription = new URL(window.location.href).searchParams.get(ERROR_DESCRIPTION);
if (error) {
const url = new URL(window.location.href);
url.searchParams.delete(ERROR);
url.searchParams.delete(ERROR_DESCRIPTION);
history.pushState(null, document.title, url.toString());
throw new AsgardeoAuthException("SPA-AUTH_HELPER-SI-SE01", error, errorDescription !== null && errorDescription !== void 0 ? errorDescription : "");
}
if (config.storage === Storage.WebWorker && tryRetrievingUserInfo) {
const basicUserInfo = yield tryRetrievingUserInfo();
if (basicUserInfo) {
return basicUserInfo;
}
}
});
}
attachTokenToRequestConfig(request) {
return __awaiter(this, void 0, void 0, function* () {
const requestConfig = Object.assign({ attachToken: true }, request);
if (requestConfig.attachToken) {
if (requestConfig.shouldAttachIDPAccessToken) {
request.headers = Object.assign(Object.assign({}, request.headers), { Authorization: `Bearer ${yield this.getIDPAccessToken()}` });
}
else {
request.headers = Object.assign(Object.assign({}, request.headers), { Authorization: `Bearer ${yield this.getAccessToken()}` });
}
}
});
}
getBasicUserInfo() {
return __awaiter(this, void 0, void 0, function* () {
return this._authenticationClient.getBasicUserInfo();
});
}
getDecodedIDToken() {
return __awaiter(this, void 0, void 0, function* () {
return this._authenticationClient.getDecodedIDToken();
});
}
getDecodedIDPIDToken() {
return __awaiter(this, void 0, void 0, function* () {
return this._authenticationClient.getDecodedIDToken();
});
}
getCryptoHelper() {
return __awaiter(this, void 0, void 0, function* () {
return this._authenticationClient.getCryptoHelper();
});
}
getIDToken() {
return __awaiter(this, void 0, void 0, function* () {
return this._authenticationClient.getIDToken();
});
}
getOIDCServiceEndpoints() {
return __awaiter(this, void 0, void 0, function* () {
return this._authenticationClient.getOIDCServiceEndpoints();
});
}
getAccessToken() {
return __awaiter(this, void 0, void 0, function* () {
return this._authenticationClient.getAccessToken();
});
}
getIDPAccessToken() {
var _a;
return __awaiter(this, void 0, void 0, function* () {
return (_a = (yield this._dataLayer.getSessionData())) === null || _a === void 0 ? void 0 : _a.access_token;
});
}
getDataLayer() {
return this._dataLayer;
}
isAuthenticated() {
return __awaiter(this, void 0, void 0, function* () {
return this._authenticationClient.isAuthenticated();
});
}
}
//# sourceMappingURL=authentication-helper.js.map