diff --git a/src/components/forms/EditAssignmentForm/EditAssignmentForm.js b/src/components/forms/EditAssignmentForm/EditAssignmentForm.js index 919dd8e81..4182a261f 100644 --- a/src/components/forms/EditAssignmentForm/EditAssignmentForm.js +++ b/src/components/forms/EditAssignmentForm/EditAssignmentForm.js @@ -430,7 +430,10 @@ const validate = ({ if (pointsPercentualThreshold) { const numericThreshold = Number(pointsPercentualThreshold); - if (pointsPercentualThreshold !== Math.round(numericThreshold).toString()) { + if ( + pointsPercentualThreshold.toString() !== + Math.round(numericThreshold).toString() + ) { errors['pointsPercentualThreshold'] = ( this.onFocus()} inputProps={{ disabled }} />{' '} diff --git a/src/pages/EditAssignment/EditAssignment.js b/src/pages/EditAssignment/EditAssignment.js index 65498e66b..1d36b12fa 100644 --- a/src/pages/EditAssignment/EditAssignment.js +++ b/src/pages/EditAssignment/EditAssignment.js @@ -10,8 +10,7 @@ import PageContent from '../../components/layout/PageContent'; import ResourceRenderer from '../../components/helpers/ResourceRenderer'; import EditAssignmentForm from '../../components/forms/EditAssignmentForm'; -import DeleteAssignmentButtonContainer - from '../../containers/DeleteAssignmentButtonContainer'; +import DeleteAssignmentButtonContainer from '../../containers/DeleteAssignmentButtonContainer'; import Box from '../../components/widgets/Box'; import { LoadingIcon, WarningIcon } from '../../components/icons'; @@ -21,14 +20,10 @@ import { } from '../../redux/modules/assignments'; import { getAssignment } from '../../redux/selectors/assignments'; import { canSubmitSolution } from '../../redux/selectors/canSubmit'; -import { - runtimeEnvironmentsSelector -} from '../../redux/selectors/runtimeEnvironments'; +import { runtimeEnvironmentsSelector } from '../../redux/selectors/runtimeEnvironments'; import { isSubmitting } from '../../redux/selectors/submission'; import { loggedInUserIdSelector } from '../../redux/selectors/auth'; -import { - fetchRuntimeEnvironments -} from '../../redux/modules/runtimeEnvironments'; +import { fetchRuntimeEnvironments } from '../../redux/modules/runtimeEnvironments'; import { isReady, getJsData } from '../../redux/helpers/resourceManager'; import withLinks from '../../hoc/withLinks'; @@ -58,8 +53,8 @@ class EditAssignment extends Component { pointsPercentualThreshold, ...rest }) => ({ - firstDeadline: moment(firstDeadline * 1000), - secondDeadline: moment(secondDeadline * 1000), + firstDeadline: moment.unix(firstDeadline), + secondDeadline: moment.unix(secondDeadline), pointsPercentualThreshold: pointsPercentualThreshold * 100, ...rest }); @@ -112,8 +107,7 @@ class EditAssignment extends Component { } >

- - {' '} + {' '}

- - {' '} + {' '} - {data => ( + {data =>

editAssignment(data.version, formData)} formValues={formValues} /> -
- )} + }
{ - + diff --git a/src/redux/helpers/api/tools.js b/src/redux/helpers/api/tools.js index b7d2bdcef..ab71dc6e8 100644 --- a/src/redux/helpers/api/tools.js +++ b/src/redux/helpers/api/tools.js @@ -2,6 +2,9 @@ import statusCode from 'statuscode'; import { addNotification } from '../../modules/notifications'; import flatten from 'flat'; +import { logout } from '../../modules/auth'; +import { isTokenValid, decode } from '../../helpers/token'; + export const isTwoHundredCode = status => statusCode.accept(status, '2xx'); export const isServerError = status => statusCode.accept(status, '5xx'); export const isUnauthorized = status => status === 403; @@ -73,15 +76,33 @@ export const createApiCallPromise = ( query = {}, method = 'GET', headers = {}, + accessToken = '', body = undefined, wasSuccessful = () => true, doNotProcess = false }, dispatch = undefined ) => { - let call = createRequest(endpoint, query, method, headers, body).catch(err => - detectUnreachableServer(err, dispatch) - ); + let call = createRequest(endpoint, query, method, headers, body) + .catch(err => detectUnreachableServer(err, dispatch)) + .then(res => { + if ( + res.status === 401 && + !isTokenValid(decode(accessToken)) && + dispatch + ) { + dispatch(logout('/')); + dispatch( + addNotification( + 'Your session expired and you were automatically logged out of the ReCodEx system.', + false + ) + ); + return Promise.reject(res); + } + + return res; + }); // this processing can be manually skipped return doNotProcess === true ? call : processResponse(call, dispatch); diff --git a/src/redux/selectors/auth.js b/src/redux/selectors/auth.js index fd12ad76e..c572a289a 100644 --- a/src/redux/selectors/auth.js +++ b/src/redux/selectors/auth.js @@ -1,5 +1,6 @@ import { createSelector } from 'reselect'; import { statusTypes } from '../modules/auth'; +import { isTokenValid } from '../helpers/token'; const getAuth = state => state.auth; const getAccessToken = auth => auth.get('accessToken'); @@ -43,8 +44,10 @@ export const hasSucceeded = service => status => status === statusTypes.LOGGED_IN ); -export const isLoggedIn = createSelector(getAuth, auth => - Boolean(auth.get('userId')) +export const isLoggedIn = createSelector( + getAuth, + auth => + Boolean(auth.get('userId')) && isTokenValid(auth.get('accessToken').toJS()) ); export const isChanging = createSelector( diff --git a/test/redux/modules/auth-test.js b/test/redux/modules/auth-test.js index db0ce2cf3..485305733 100644 --- a/test/redux/modules/auth-test.js +++ b/test/redux/modules/auth-test.js @@ -99,7 +99,8 @@ describe('Authentication', () => { it('must return LOGGED OUT initial state when an expired access token is given', () => { // the token const exp = 1491903618 * 1000; - const expiredToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTE5MDM2MTh9.3iH9ZXaaACF0Jugajfv4TggeUcqJzPQYqGveh16WHkU'; + const expiredToken = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTE5MDM2MTh9.3iH9ZXaaACF0Jugajfv4TggeUcqJzPQYqGveh16WHkU'; const reducer = reducerFactory(expiredToken, exp + 1000); // +1 second const state = reducer(undefined, {}); @@ -114,7 +115,8 @@ describe('Authentication', () => { it('must return LOGGED IN initial state when a valid access token is given', () => { const exp = 1491903618 * 1000; - const validToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTE5MDM2MTgsInN1YiI6MTIzfQ._Er1LBGLVnD3bdg439fgL7E1YcnMgTDYtzfgjQrQrXQ'; + const validToken = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTE5MDM2MTgsInN1YiI6MTIzfQ._Er1LBGLVnD3bdg439fgL7E1YcnMgTDYtzfgjQrQrXQ'; const reducer = reducerFactory(validToken, exp - 1000); // -1 second const state = reducer(undefined, {}); const expectedState = fromJS({ @@ -142,7 +144,10 @@ describe('Authentication', () => { it('must detect that the user is logged in', () => { const state = { auth: fromJS({ - userId: 123 + userId: 123, + accessToken: { + exp: Date.now() / 1000 + 100 + } }) };