Skip to content

Commit

Permalink
Merge branch 'master' into dryd-1284
Browse files Browse the repository at this point in the history
  • Loading branch information
ray-lee authored Jan 11, 2024
2 parents 24dc6fc + 894f53f commit cf6fa7f
Show file tree
Hide file tree
Showing 47 changed files with 1,858 additions and 481 deletions.
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cspace-ui",
"version": "9.0.0-dev.2",
"version": "9.0.0-dev.5",
"description": "CollectionSpace user interface for browsers",
"author": "Ray Lee <ray.lee@lyrasis.org>",
"license": "ECL-2.0",
Expand Down Expand Up @@ -43,7 +43,7 @@
},
"dependencies": {
"classnames": "^2.2.5",
"cspace-client": "^2.0.0-rc.1",
"cspace-client": "^2.0.0-rc.2",
"cspace-input": "^2.0.4",
"cspace-layout": "^2.0.2",
"cspace-refname": "^1.0.4",
Expand Down
29 changes: 4 additions & 25 deletions src/actions/cspace.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ import cspaceClient from 'cspace-client';
import get from 'lodash/get';
import { MODAL_LOGIN } from '../constants/modalNames';
import getSession, { storeSession } from '../helpers/session';
import { loadPrefs } from './prefs';
import { readAccountPerms, readAccountRoles } from './account';
import { readAuthVocabs } from './authority';
import { openModal } from './notification';

import {
Expand Down Expand Up @@ -33,6 +30,9 @@ export const setSession = (newSession) => {
return {
type: CSPACE_CONFIGURED,
payload: getSession().config(),
meta: {
username: getSession().username(),
},
};
};

Expand Down Expand Up @@ -77,26 +77,5 @@ export const configureCSpace = (config) => (dispatch) => {

dispatch(setSession(newSession));

const username = newSession.username();

if (!username) {
return Promise.resolve();
}

return dispatch(readAccountPerms(config))
.then(() => dispatch(readAccountRoles(config, username)))
.then(() => dispatch(loadPrefs(config, username)))
.then(() => dispatch(readAuthVocabs(config)))
.catch((error) => {
// 401 is expected if the user's auth token has expired. The client onError handler will take
// care of showing a notification, so the error can be swallowed here.

const status = get(error, ['response', 'status']);

if (status === 401) {
return Promise.resolve();
}

return Promise.reject(error);
});
return Promise.resolve();
};
55 changes: 53 additions & 2 deletions src/actions/login.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,13 @@ import {
LOGIN_STARTED,
LOGIN_FULFILLED,
LOGIN_REJECTED,
LOGIN_WINDOW_OPENED,
LOGIN_WINDOW_OPEN_FAILED,
LOGIN_WINDOW_CLOSED,
} from '../constants/actionCodes';

export const LOGIN_WINDOW_NAME = 'cspace-login';

const renewAuth = (config, authCode, authCodeRequestData = {}) => (dispatch) => {
const {
codeVerifier,
Expand Down Expand Up @@ -213,14 +218,15 @@ export const login = (config, authCode, authCodeRequestData = {}) => (dispatch,
};

/**
* Log in using a fulfilled authorization code request.
* 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
*/
export const loginWithAuthCodeRequest = (
export const receiveAuthCode = (
config,
authCodeRequestId,
authCode,
Expand All @@ -242,5 +248,50 @@ export const loginWithAuthCodeRequest = (

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));
};

export 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: LOGIN_WINDOW_OPEN_FAILED,
};
}

return {
type: LOGIN_WINDOW_OPENED,
};
};

export const loginWindowClosed = () => ({
type: LOGIN_WINDOW_CLOSED,
});
126 changes: 126 additions & 0 deletions src/components/login/AuthStatus.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import React from 'react';
import PropTypes from 'prop-types';
import Immutable from 'immutable';
import { defineMessages, FormattedMessage } from 'react-intl';
import Notification from '../notification/Notification';
import styles from '../../../styles/cspace-ui/AuthStatus.css';

const propTypes = {
isPending: PropTypes.bool,
isSuccess: PropTypes.bool,
error: PropTypes.instanceOf(Immutable.Map),
username: PropTypes.string,
};

const messages = defineMessages({
pending: {
id: 'authStatus.pending',
description: 'Message displayed while login is in progress.',
defaultMessage: 'Finishing sign in...',
},
success: {
id: 'authStatus.success',
description: 'Message displayed when login completes successfully.',
defaultMessage: 'Signed in as {username}.',
},
error: {
id: 'authStatus.error',
description: 'Generic login error message. Displayed when a more specific error message is not available.',
defaultMessage: 'Sign in failed.',
},
ERR_AUTH_CODE_REQUEST_NOT_FOUND: {
id: 'authStatus.ERR_AUTH_CODE_REQUEST_NOT_FOUND',
description: 'Error message displayed when an invalid authorization code request ID was received.',
defaultMessage: 'Sign in failed. The authorization code does not belong to an active sign in request.',
},
ERR_BAD_REQUEST: {
id: 'authStatus.ERR_BAD_REQUEST',
description: 'Error message displayed when a bad request response was received during login.',
defaultMessage: 'Sign in failed. The CollectionSpace server received a bad request.',
},
ERR_ACCOUNT_INACTIVE: {
id: 'authStatus.ERR_ACCOUNT_INACTIVE',
description: 'Error message displayed when the account is inactive during login.',
defaultMessage: 'Sign in failed. The account is inactive.',
},
ERR_ACCOUNT_INVALID: {
id: 'authStatus.ERR_ACCOUNT_INVALID',
description: 'Error message displayed when the account is in an invalid state during login.',
defaultMessage: 'Sign in failed. The account is in an invalid state.',
},
ERR_ACCOUNT_NOT_FOUND: {
id: 'authStatus.ERR_ACCOUNT_NOT_FOUND',
description: 'Error message displayed when the account is not found during login.',
defaultMessage: 'Sign in failed. Account not found.',
},
ERR_INVALID_CREDENTIALS: {
id: 'authStatus.ERR_INVALID_CREDENTIALS',
description: 'Error message displayed when incorrect credentials were entered during login.',
defaultMessage: 'Sign in failed. Incorrect username/password.',
},
ERR_NETWORK: {
id: 'authStatus.ERR_NETWORK',
description: 'Error message displayed when there is a network error during login.',
defaultMessage: 'Sign in failed. Unable to reach the CollectionSpace server.',
},
ERR_WRONG_TENANT: {
id: 'authStatus.ERR_WRONG_TENANT',
description: 'Error message displayed when the logged in user belongs to the wrong tenant.',
defaultMessage: 'Sign in failed. The user is not registered to this CollectionSpace tenant.',
},
});

const renderStatus = (username, isSuccess, isPending) => {
let messageKey;

if (isPending) {
messageKey = 'pending';
} else if (isSuccess) {
messageKey = 'success';
}

if (!messageKey) {
return null;
}

return (
<p><FormattedMessage {...messages[messageKey]} values={{ username }} /></p>
);
};

const renderError = (error, isPending) => {
if (isPending || !error) {
return undefined;
}

const messageKey = error.get('code') || 'error';

return (
<Notification
id="authStatus.error"
items={[{
message: messages[messageKey] || messages.error,
}]}
showCloseButton={false}
status="error"
/>
);
};

export default function AuthStatus(props) {
const {
error,
isPending,
isSuccess,
username,
} = props;

return (
<div className={styles.common}>
{renderStatus(username, isSuccess, isPending)}
{renderError(error, isPending)}
</div>
);
}

AuthStatus.propTypes = propTypes;
Loading

0 comments on commit cf6fa7f

Please sign in to comment.