cspace-ui
Version:
CollectionSpace user interface for browsers
223 lines (216 loc) • 8.26 kB
JavaScript
;
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;