UNPKG

@brionmario-experimental/asgardeo-auth-spa

Version:

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

478 lines (408 loc) 17.1 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. */ import { AUTHORIZATION_CODE, AsgardeoAuthClient, AuthClientConfig, AuthenticationUtils, BasicUserInfo, CryptoHelper, DataLayer, DecodedIDTokenPayload, FetchResponse, GetAuthURLConfig, OIDCEndpoints, ResponseMode, SESSION_STATE, STATE, SessionData, Store } from "@asgardeo/auth-js"; import { SILENT_SIGN_IN_STATE, Storage, TOKEN_REQUEST_CONFIG_KEY } from "../constants"; import { AuthenticationHelper, SPAHelper, SessionManagementHelper } from "../helpers"; import { HttpClient, HttpClientInstance } from "../http-client"; import { HttpError, HttpRequestConfig, HttpResponse, MainThreadClientConfig, MainThreadClientInterface } from "../models"; import { SPACustomGrantConfig } from "../models/request-custom-grant"; import { LocalStore, MemoryStore, SessionStore } from "../stores"; import { SPAUtils } from "../utils"; import { SPACryptoUtils } from "../utils/crypto-utils"; const initiateStore = (store: Storage | undefined): 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 MainThreadClient = async ( instanceID: number, config: AuthClientConfig<MainThreadClientConfig>, getAuthHelper: ( authClient: AsgardeoAuthClient<MainThreadClientConfig>, spaHelper: SPAHelper<MainThreadClientConfig> ) => AuthenticationHelper<MainThreadClientConfig> ): Promise<MainThreadClientInterface> => { const _store: Store = initiateStore(config.storage); const _cryptoUtils: SPACryptoUtils = new SPACryptoUtils(); const _authenticationClient = new AsgardeoAuthClient<MainThreadClientConfig>(); await _authenticationClient.initialize(config, _store, _cryptoUtils, instanceID); const _spaHelper = new SPAHelper<MainThreadClientConfig>(_authenticationClient); const _dataLayer = _authenticationClient.getDataLayer(); const _sessionManagementHelper = await SessionManagementHelper( async () => { return _authenticationClient.getSignOutURL(); }, config.storage ?? Storage.SessionStorage, (sessionState: string) => _dataLayer.setSessionDataParameter(SESSION_STATE as keyof SessionData, sessionState ?? "") ); const _authenticationHelper = getAuthHelper(_authenticationClient, _spaHelper); let _getSignOutURLFromSessionStorage: boolean = false; const _httpClient: HttpClientInstance = HttpClient.getInstance(); let _isHttpHandlerEnabled: boolean = true; let _httpErrorCallback: (error: HttpError) => void | Promise<void>; let _httpFinishCallback: () => void; const attachToken = async (request: HttpRequestConfig): Promise<void> => { await _authenticationHelper.attachTokenToRequestConfig(request); } _httpClient?.init && (await _httpClient.init(true, attachToken)); const setHttpRequestStartCallback = (callback: () => void): void => { _httpClient?.setHttpRequestStartCallback && _httpClient.setHttpRequestStartCallback(callback); }; const setHttpRequestSuccessCallback = (callback: (response: HttpResponse) => void): void => { _httpClient?.setHttpRequestSuccessCallback && _httpClient.setHttpRequestSuccessCallback(callback); }; const setHttpRequestFinishCallback = (callback: () => void): void => { _httpClient?.setHttpRequestFinishCallback && _httpClient.setHttpRequestFinishCallback(callback); }; const setHttpRequestErrorCallback = (callback: (error: HttpError) => void | Promise<void>): void => { _httpErrorCallback = callback; }; const httpRequest = async (requestConfig: HttpRequestConfig): Promise<HttpResponse> => { return await _authenticationHelper.httpRequest( _httpClient, requestConfig, _isHttpHandlerEnabled, _httpErrorCallback, _httpFinishCallback ); }; const httpRequestAll = async (requestConfigs: HttpRequestConfig[]): Promise<HttpResponse[] | undefined> => { return await _authenticationHelper.httpRequestAll( requestConfigs, _httpClient, _isHttpHandlerEnabled, _httpErrorCallback, _httpFinishCallback ); }; const getHttpClient = (): HttpClientInstance => { return _httpClient; }; const enableHttpHandler = (): boolean => { _authenticationHelper.enableHttpHandler(_httpClient); _isHttpHandlerEnabled = true; return true; }; const disableHttpHandler = (): boolean => { _authenticationHelper.disableHttpHandler(_httpClient); _isHttpHandlerEnabled = false; return true; }; const checkSession = async (): Promise<void> => { const oidcEndpoints: OIDCEndpoints = await _authenticationClient.getOIDCServiceEndpoints(); const config = await _dataLayer.getConfigData(); _authenticationHelper.initializeSessionManger( config, oidcEndpoints, async () => (await _authenticationClient.getBasicUserInfo()).sessionState, async (params?: GetAuthURLConfig): Promise<string> => _authenticationClient.getAuthorizationURL(params), _sessionManagementHelper ); }; const shouldStopAuthn = async (): Promise<boolean> => { return await _sessionManagementHelper.receivePromptNoneResponse( async (sessionState: string | null) => { await _dataLayer.setSessionDataParameter(SESSION_STATE as keyof SessionData, sessionState ?? ""); return; } ); } const setSessionStatus = async (sessionStatus: string): Promise<void> => { await _dataLayer.setSessionStatus(sessionStatus); } const signIn = async ( signInConfig?: GetAuthURLConfig, authorizationCode?: string, sessionState?: string, state?: string, tokenRequestConfig?: { params: Record<string, unknown> } ): Promise<BasicUserInfo> => { const basicUserInfo = await _authenticationHelper.handleSignIn( shouldStopAuthn, checkSession, undefined ); if(basicUserInfo) { return basicUserInfo; } else { let resolvedAuthorizationCode: string; let resolvedSessionState: string; let resolvedState: string; let resolvedTokenRequestConfig: { params: Record<string, unknown> } = { params: {} }; if (config?.responseMode === ResponseMode.formPost && authorizationCode) { resolvedAuthorizationCode = authorizationCode; resolvedSessionState = sessionState ?? ""; resolvedState = state ?? ""; } else { resolvedAuthorizationCode = new URL(window.location.href).searchParams.get(AUTHORIZATION_CODE) ?? ""; resolvedSessionState = new URL(window.location.href).searchParams.get(SESSION_STATE) ?? ""; resolvedState = new URL(window.location.href).searchParams.get(STATE) ?? ""; SPAUtils.removeAuthorizationCode(); } if (resolvedAuthorizationCode && resolvedState) { setSessionStatus("true"); const storedTokenRequestConfig = await _dataLayer.getTemporaryDataParameter(TOKEN_REQUEST_CONFIG_KEY); if (storedTokenRequestConfig && typeof storedTokenRequestConfig === "string") { resolvedTokenRequestConfig = JSON.parse(storedTokenRequestConfig); } return requestAccessToken(resolvedAuthorizationCode, resolvedSessionState, resolvedState, resolvedTokenRequestConfig); } return _authenticationClient.getAuthorizationURL(signInConfig).then(async (url: string) => { if (config.storage === Storage.BrowserMemory && config.enablePKCE) { const pkceKey: string = AuthenticationUtils.extractPKCEKeyFromStateParam(resolvedState); SPAUtils.setPKCE(pkceKey, (await _authenticationClient.getPKCECode(resolvedState)) as string); } if (tokenRequestConfig) { _dataLayer.setTemporaryDataParameter(TOKEN_REQUEST_CONFIG_KEY, JSON.stringify(tokenRequestConfig)); } location.href = url; await SPAUtils.waitTillPageRedirect(); return Promise.resolve({ allowedScopes: "", displayName: "", email: "", sessionState: "", sub: "", tenantDomain: "", username: "" }); }); } }; const signOut = async (): Promise<boolean> => { if ((await _authenticationClient.isAuthenticated()) && !_getSignOutURLFromSessionStorage) { location.href = await _authenticationClient.getSignOutURL(); } else { location.href = SPAUtils.getSignOutURL(config.clientID, instanceID); } _spaHelper.clearRefreshTokenTimeout(); await _dataLayer.removeOIDCProviderMetaData(); await _dataLayer.removeTemporaryData(); await _dataLayer.removeSessionData(); await _dataLayer.removeSessionStatus(); await SPAUtils.waitTillPageRedirect(); return true; }; const enableRetrievingSignOutURLFromSession = (config: SPACustomGrantConfig) => { if (config.preventSignOutURLUpdate) { _getSignOutURLFromSessionStorage = true; } } const requestCustomGrant = async (config: SPACustomGrantConfig): Promise<BasicUserInfo | FetchResponse> => { return await _authenticationHelper.requestCustomGrant( config, enableRetrievingSignOutURLFromSession ); }; const refreshAccessToken = async (): Promise<BasicUserInfo> => { try { return await _authenticationHelper.refreshAccessToken( enableRetrievingSignOutURLFromSession ); } catch (error) { return Promise.reject(error); } }; const revokeAccessToken = async (): Promise<boolean> => { const timer: number = await _spaHelper.getRefreshTimeoutTimer(); return _authenticationClient .revokeAccessToken() .then(() => { _sessionManagementHelper.reset(); _spaHelper.clearRefreshTokenTimeout(timer); return Promise.resolve(true); }) .catch((error) => Promise.reject(error)); }; const requestAccessToken = async ( resolvedAuthorizationCode: string, resolvedSessionState: string, resolvedState: string, tokenRequestConfig?: { params: Record<string, unknown> } ): Promise<BasicUserInfo> => { return await _authenticationHelper.requestAccessToken( resolvedAuthorizationCode, resolvedSessionState, checkSession, undefined, resolvedState, tokenRequestConfig ); }; const constructSilentSignInUrl = async ( additionalParams: Record<string, string | boolean> = {} ): Promise<string> => { const config = await _dataLayer.getConfigData(); const urlString: string = await _authenticationClient.getAuthorizationURL({ prompt: "none", state: SILENT_SIGN_IN_STATE, ...additionalParams }); // Replace form_post with query const urlObject = new URL(urlString); urlObject.searchParams.set("response_mode", "query"); const url: string = urlObject.toString(); if (config.storage === Storage.BrowserMemory && config.enablePKCE) { const state = urlObject.searchParams.get(STATE); SPAUtils.setPKCE( AuthenticationUtils.extractPKCEKeyFromStateParam(state ?? ""), (await _authenticationClient.getPKCECode(state ?? "")) as string ); } 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 = async ( additionalParams?: Record<string, string | boolean>, tokenRequestConfig?: { params: Record<string, unknown> } ): Promise<BasicUserInfo | boolean> => { return await _authenticationHelper.trySignInSilently( constructSilentSignInUrl, requestAccessToken, _sessionManagementHelper, additionalParams, tokenRequestConfig ); }; const getBasicUserInfo = async (): Promise<BasicUserInfo> => { return _authenticationHelper.getBasicUserInfo(); }; const getDecodedIDToken = async (): Promise<DecodedIDTokenPayload> => { return _authenticationHelper.getDecodedIDToken(); }; const getCryptoHelper = async (): Promise<CryptoHelper> => { return _authenticationHelper.getCryptoHelper(); }; const getIDToken = async (): Promise<string> => { return _authenticationHelper.getIDToken(); }; const getOIDCServiceEndpoints = async (): Promise<OIDCEndpoints> => { return _authenticationHelper.getOIDCServiceEndpoints(); }; const getAccessToken = async (): Promise<string> => { return _authenticationHelper.getAccessToken(); }; const getDataLayer = async (): Promise<DataLayer<MainThreadClientConfig>> => { return _authenticationHelper.getDataLayer(); }; const getConfigData = async (): Promise<AuthClientConfig<MainThreadClientConfig>> => { return await _dataLayer.getConfigData(); }; const isAuthenticated = async (): Promise<boolean> => { return _authenticationHelper.isAuthenticated(); }; const isSessionActive = async (): Promise<boolean> => { return await _dataLayer.getSessionStatus() === "true"; }; const updateConfig = async (newConfig: Partial<AuthClientConfig<MainThreadClientConfig>>): Promise<void> => { const existingConfig = await _dataLayer.getConfigData(); const isCheckSessionIframeDifferent: boolean = !( existingConfig && existingConfig.endpoints && existingConfig.endpoints.checkSessionIframe && newConfig && newConfig.endpoints && newConfig.endpoints.checkSessionIframe && existingConfig.endpoints.checkSessionIframe === newConfig.endpoints.checkSessionIframe ); const config = { ...existingConfig, ...newConfig }; await _authenticationClient.updateConfig(config); // Re-initiates check session if the check session endpoint is updated. if (config.enableOIDCSessionManagement && isCheckSessionIframeDifferent) { _sessionManagementHelper.reset(); checkSession(); } }; return { disableHttpHandler, enableHttpHandler, getAccessToken, getBasicUserInfo, getConfigData, getCryptoHelper, getDataLayer, getDecodedIDToken, getHttpClient, getIDToken, getOIDCServiceEndpoints, httpRequest, httpRequestAll, isAuthenticated, isSessionActive, refreshAccessToken, requestCustomGrant, revokeAccessToken, setHttpRequestErrorCallback, setHttpRequestFinishCallback, setHttpRequestStartCallback, setHttpRequestSuccessCallback, signIn, signOut, trySignInSilently, updateConfig }; };