UNPKG

cspace-ui

Version:
223 lines (216 loc) 8.26 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.receiveAuthCode = exports.openLoginWindow = exports.loginWindowClosed = exports.login = exports.createAuthCodeUrl = exports.LOGIN_WINDOW_NAME = void 0; var _get = _interopRequireDefault(require("lodash/get")); var _qs = _interopRequireDefault(require("qs")); var _authority = require("./authority"); var _cspace = require("./cspace"); var _prefs = require("./prefs"); var _account = require("./account"); var _tags = _interopRequireDefault(require("./tags")); var _reducers = require("../reducers"); var _errorCodes = require("../constants/errorCodes"); var _actionCodes = require("../constants/actionCodes"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /* global window */ const LOGIN_WINDOW_NAME = exports.LOGIN_WINDOW_NAME = 'cspace-login'; const renewAuth = (config, authCode, authCodeRequestData = {}) => dispatch => { const { codeVerifier, redirectUri } = authCodeRequestData; const session = (0, _cspace.createSession)(authCode, codeVerifier, redirectUri); const loginPromise = authCode ? session.login() : Promise.resolve(); let username = null; return loginPromise.then(() => session.read('accounts/0/accountperms')).then(response => { if ((0, _get.default)(response, ['data', 'ns2:account_permission', 'account', 'tenantId']) !== config.tenantId) { // The logged in user doesn't belong to the tenant that this UI expects. return session.logout().finally(() => { const error = new Error(); error.code = _errorCodes.ERR_WRONG_TENANT; return Promise.reject(error); }); } username = (0, _get.default)(response, ['data', 'ns2:account_permission', 'account', 'userId']); dispatch((0, _cspace.setSession)(session)); return dispatch({ type: _actionCodes.AUTH_RENEW_FULFILLED, payload: response, meta: { config, username } }); }).then(() => dispatch((0, _account.readAccountRoles)(config, username))).then(() => Promise.resolve(username)).catch(error => { let { code } = error; const data = (0, _get.default)(error, ['response', 'data']) || ''; if (/invalid state/.test(data)) { code = _errorCodes.ERR_ACCOUNT_INVALID; } else if (/inactive/.test(data)) { code = _errorCodes.ERR_ACCOUNT_INACTIVE; } else if (/account not found/.test(data)) { code = _errorCodes.ERR_ACCOUNT_NOT_FOUND; } else { const desc = (0, _get.default)(error, ['response', 'data', 'error_description']) || (0, _get.default)(error, 'message'); if (desc === 'Network Error') { code = _errorCodes.ERR_NETWORK; } } dispatch({ type: _actionCodes.AUTH_RENEW_REJECTED, payload: { code, error }, meta: { username } }); const wrapper = new Error(); wrapper.code = code; wrapper.error = error; return Promise.reject(wrapper); }); }; const generateS256Hash = async input => { const inputBytes = new TextEncoder().encode(input); const sha256Bytes = await window.crypto.subtle.digest('SHA-256', inputBytes); const base64 = window.btoa(String.fromCharCode(...new Uint8Array(sha256Bytes))); const urlSafeBase64 = base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); return urlSafeBase64; }; const authCodeRequestRedirectUrl = serverUrl => { const currentUrl = window.location.href; const authorizedUrl = new URL('authorized', currentUrl); if (!serverUrl) { // Note: The "/.." prefix is needed because Spring Security OAuth appears to be appending // to the base path of the services layer when sending redirects, so "/cspace" becomes // "/cspace-services/cspace". The "/.." works around that, until I can figure out how to // configure Spring to do something different. return `/..${authorizedUrl.pathname}`; } return authorizedUrl.toString(); }; const authCodeRequestStorageKey = requestId => `authCodeRequest:${requestId}`; const createAuthCodeUrl = (config, landingPath) => async dispatch => { const { serverUrl } = config; const requestId = window.crypto.randomUUID(); const codeVerifier = window.crypto.randomUUID(); const codeChallenge = await generateS256Hash(codeVerifier); const redirectUri = authCodeRequestRedirectUrl(serverUrl); const requestData = { codeVerifier, landingPath, redirectUri }; window.sessionStorage.setItem(authCodeRequestStorageKey(requestId), JSON.stringify(requestData)); const params = { response_type: 'code', client_id: 'cspace-ui', scope: 'cspace.full', redirect_uri: redirectUri, state: requestId, code_challenge: codeChallenge, code_challenge_method: 'S256', tid: config.tenantId }; const queryString = _qs.default.stringify(params); const url = `${serverUrl}/cspace-services/oauth2/authorize?${queryString}`; dispatch({ type: _actionCodes.AUTH_CODE_URL_CREATED, payload: url }); return url; }; /** * Log in, using either the saved user or an authorization code. * * @param {*} config * @param {*} authCode The authorization code. If undefined, the stored user will be used. * @param {*} requestData The data that was used to retrieve the authorization code. * @returns */ exports.createAuthCodeUrl = createAuthCodeUrl; const login = (config, authCode, authCodeRequestData = {}) => (dispatch, getState) => { const prevUsername = (0, _reducers.getUserUsername)(getState()); dispatch((0, _prefs.savePrefs)()); dispatch({ type: _actionCodes.LOGIN_STARTED }); let username; return dispatch(renewAuth(config, authCode, authCodeRequestData)).then(loggedInUsername => { username = loggedInUsername; return Promise.resolve(); }).then(() => dispatch((0, _prefs.loadPrefs)(config, username))).then(() => dispatch((0, _tags.default)())).then(() => dispatch((0, _authority.readAuthVocabs)(config))).then(() => dispatch({ type: _actionCodes.LOGIN_FULFILLED, meta: { landingPath: authCodeRequestData.landingPath, prevUsername, username } })).catch(error => dispatch({ type: _actionCodes.LOGIN_REJECTED, payload: error })); }; /** * Receive an authorization code from the OAuth server. This will have been sent in a redirect from * the server, in response to an authorization code request. * * @param {*} config * @param {*} authCodeRequestId * @param {*} authCode * @returns */ exports.login = login; const receiveAuthCode = (config, authCodeRequestId, authCode) => async dispatch => { const storageKey = authCodeRequestStorageKey(authCodeRequestId); const authCodeRequestDataJson = window.sessionStorage.getItem(storageKey); window.sessionStorage.removeItem(storageKey); if (!authCodeRequestDataJson) { const error = new Error(); error.code = _errorCodes.ERR_AUTH_CODE_REQUEST_NOT_FOUND; return dispatch({ type: _actionCodes.LOGIN_REJECTED, payload: error }); } const authCodeRequestData = JSON.parse(authCodeRequestDataJson); if (window.name === LOGIN_WINDOW_NAME && window.opener && window.opener.onAuthCodeReceived != null) { // If this is a pop-up, send the auth code and request data to the parent, and close this // window. window.opener.onAuthCodeReceived(authCode, authCodeRequestData); window.close(); return undefined; } return dispatch(login(config, authCode, authCodeRequestData)); }; exports.receiveAuthCode = receiveAuthCode; const openLoginWindow = url => { const popupWidth = 550; const popupHeight = 800; const screenWidth = window.screen.width; const screenHeight = window.screen.height; const left = (screenWidth - popupWidth) / 2; const top = (screenHeight - popupHeight) / 2; const popup = window.open(url, LOGIN_WINDOW_NAME, `width=${popupWidth},height=${popupHeight},left=${left},top=${top}`); if (!popup) { return { type: _actionCodes.LOGIN_WINDOW_OPEN_FAILED }; } return { type: _actionCodes.LOGIN_WINDOW_OPENED }; }; exports.openLoginWindow = openLoginWindow; const loginWindowClosed = () => ({ type: _actionCodes.LOGIN_WINDOW_CLOSED }); exports.loginWindowClosed = loginWindowClosed;