Skip to content
This repository has been archived by the owner on Jul 17, 2020. It is now read-only.

Logout on expired sessions #325

Merged
merged 4 commits into from
Nov 21, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions client/Application.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,21 @@ class Application extends Component {
}

componentDidUpdate() {
const {user} = this.props
window.dispatchEvent(new Event('resize'))
if (user && user.roles.find(r => r === ADMIN_ROLE)) {
$('body').removeClass('layout-top-nav')
}
}

render() {
if (this.state.loaded) return <Router history={this.props.history} />
if (this.state.loaded) {
const {user} = this.props
const isAdmin = user && user.roles.find(r => r === ADMIN_ROLE)
return (
<div className={`skin-blue fixed ${!isAdmin && 'layout-top-nav'}`}>
<div className='wrapper'>
<Router history={this.props.history} />
</div>
</div>
)
}

if (this.props.error) {
return <ServerError />
Expand Down
42 changes: 42 additions & 0 deletions client/components/router/guestOrRedirect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react'
import {connect} from 'react-redux'
import {push} from 'react-router-redux'

import selectors from '../../store/selectors'
import userClientRole from '../../lib/user-client-role'

const mapStateToProps = state => ({
user: selectors.auth.getUser(state)
})

const mapDispatchToProps = dispatch => ({
push: location => dispatch(push(location))
})

const redirectIfAlreadySignedIn = props => {
if (props.user && props.user._id) {
const role = userClientRole(props.user)
props.push(role ? `/${role.split('/')[1]}s` : '/')
}
}

const guestOrRedirect = WrappedComponent => {
class GuestOrRedirect extends React.Component {
constructor(props) {
super(props)
redirectIfAlreadySignedIn(props)
}

componentWillReceiveProps(nextProps) {
redirectIfAlreadySignedIn(nextProps)
}

render() {
return <WrappedComponent {...this.props} />
}
}

return connect(mapStateToProps, mapDispatchToProps)(GuestOrRedirect)
}

export default guestOrRedirect
4 changes: 2 additions & 2 deletions client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
</head>
<body class="skin-blue fixed layout-top-nav">
<div id="app" class="wrapper"></div>
<body>
<div id="app"></div>
</body>
</html>
7 changes: 4 additions & 3 deletions client/modules/users/UserRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import ConfirmNewGoogleAccount from './components/ConfirmNewGoogleAccount'
import EditProfile from './components/EditProfile'
import EditUser from './components/EditUser'
import ForgotPassword from './components/ForgotPassword'
import guestOrRedirect from '../../components/router/guestOrRedirect'
import ResetPassword from './components/ResetPassword'
import SignIn from './components/SignIn'
import SignUp from './components/SignUp'
Expand Down Expand Up @@ -44,15 +45,15 @@ const UserRouter = ({match}) =>
<Route
path={`${match.url}/forgot-password`}
exact
component={ForgotPassword}
component={guestOrRedirect(ForgotPassword)}
/>
<Route
path={`${match.url}/reset-password/:token`}
exact
component={ResetPassword}
/>
<Route path={`${match.url}/signin`} exact component={SignIn} />
<Route path={`${match.url}/signup`} exact component={SignUp} />
<Route path={`${match.url}/signin`} exact component={guestOrRedirect(SignIn)} />
<Route path={`${match.url}/signup`} exact component={guestOrRedirect(SignUp)} />
<Route
path={`${match.url}/confirm-new-google-account`}
exact
Expand Down
16 changes: 1 addition & 15 deletions client/modules/users/components/ForgotPassword.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react'
import {connect} from 'react-redux'
import {push} from 'react-router-redux'
import {Col, Row} from 'react-bootstrap'

import selectors from '../../../store/selectors'
Expand All @@ -12,7 +11,6 @@ import LoadingWrapper from '../../../components/LoadingWrapper'
class ForgotPassword extends React.Component {
constructor(props) {
super(props)
this.redirectIfAlreadySignedIn(this.props)
this.state = {
email: ""
}
Expand All @@ -22,10 +20,6 @@ class ForgotPassword extends React.Component {
this.props.clearFlags()
}

componentWillReceiveProps = nextProps => {
this.redirectIfAlreadySignedIn(nextProps)
}

onFieldChange = e => {
const { name, value } = e.target
this.setState({ [name]: value })
Expand All @@ -36,12 +30,6 @@ class ForgotPassword extends React.Component {
this.props.resetPassword(this.state.email)
}

redirectIfAlreadySignedIn(props) {
if (props.user && props.user._id) {
props.push('/')
}
}

render = () => {
if (this.props.success) {
return (
Expand Down Expand Up @@ -91,16 +79,14 @@ class ForgotPassword extends React.Component {
}

const mapStateToProps = state => ({
user: selectors.auth.getUser(state),
fetching: selectors.auth.fetching(state),
error: selectors.auth.error(state),
success: selectors.auth.success(state)
})

const mapDispatchToProps = dispatch => ({
resetPassword: email => dispatch(forgotPassword({ email })),
clearFlags: () => dispatch(clearFlags()),
push: location => dispatch(push(location))
clearFlags: () => dispatch(clearFlags())
})

export default connect(mapStateToProps, mapDispatchToProps)(ForgotPassword)
18 changes: 1 addition & 17 deletions client/modules/users/components/SignIn.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
import React from 'react'
import {connect} from 'react-redux'
import {Link} from 'react-router-dom'
import {push} from 'react-router-redux'

import selectors from '../../../store/selectors'
import {signIn, clearFlags} from '../authReducer'
import userClientRole from '../../../lib/user-client-role'

import FieldGroup from '../../../components/form/FieldGroup'
import {LoginBox} from '../../../components/login'

export class SignIn extends React.Component {
constructor(props) {
super(props)
this.redirectIfAlreadySignedIn(this.props)
this.state = {
email: "",
password: ""
Expand All @@ -24,13 +21,6 @@ export class SignIn extends React.Component {
this.props.clearFlags()
}

redirectIfAlreadySignedIn(props) {
if (props.user && props.user._id) {
const role = userClientRole(props.user)
props.push(role ? `/${role.split('/')[1]}s` : '/')
}
}

onFieldChange = e => {
const {name, value} = e.target
this.setState({ [name]: value })
Expand All @@ -42,10 +32,6 @@ export class SignIn extends React.Component {
this.setState({password: ""})
}

componentWillReceiveProps = nextProps => {
this.redirectIfAlreadySignedIn(nextProps)
}

render = () =>
<section className="content">
<LoginBox
Expand Down Expand Up @@ -95,7 +81,6 @@ export class SignIn extends React.Component {
}

const mapStateToProps = state => ({
user: selectors.auth.getUser(state),
fetchingUser: selectors.auth.fetching(state),
fetchUserError: selectors.auth.error(state),
googleAuthentication: (state.settings && state.settings.data) ? state.settings.data.googleAuthentication : false
Expand All @@ -105,8 +90,7 @@ const mapDispatchToProps = dispatch => ({
signIn: (email, password) => {
dispatch(signIn({ email, password }))
},
clearFlags: () => dispatch(clearFlags()),
push: location => dispatch(push(location))
clearFlags: () => dispatch(clearFlags())
})

export default connect(mapStateToProps, mapDispatchToProps)(SignIn)
27 changes: 0 additions & 27 deletions client/modules/users/components/SignIn.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
import React from 'react'
import { shallow } from 'enzyme'

import { push as reactRouterReduxPush } from 'react-router-redux'

import ConnectedSignIn, { SignIn } from './SignIn'
import { signIn as authReducerSignIn, clearFlags as authReducerClearFlags } from '../authReducer'

Expand Down Expand Up @@ -55,18 +53,6 @@ describe('SignIn Class', function () {
expect(wrapper.state().email).to.eql('[email protected]')
expect(wrapper.state().password).to.eql('12345678')
})

it('calls props.push if a user is signed in', () => {
const spy = sinon.spy()
shallow(<SignIn user={{_id: 1}} push={spy} clearFlags={ () => {} } />)
expect(spy.called).to.eql(true)
})

it('does not call props.push if a user is not signed in', () => {
const spy = sinon.spy()
shallow(<SignIn user={null} push={spy} clearFlags={ () => {} } />)
expect(spy.called).to.eql(false)
})
})

describe('Connect(SignIn)', function () {
Expand Down Expand Up @@ -94,11 +80,6 @@ describe('Connect(SignIn)', function () {

})

it('gets the user from the redux store', () => {
const wrapper = shallow(<ConnectedSignIn />, {context: {store: mockStore}})
expect(wrapper.props().user).to.eql(reduxState.auth.user)
})

it ('gets the login error from the redux store', () => {
const wrapper = shallow(<ConnectedSignIn />, {context: {store: mockStore}})
expect(wrapper.props().fetchUserError).to.eql(mockStore.getState().auth.error)
Expand All @@ -122,14 +103,6 @@ describe('Connect(SignIn)', function () {
expect(actualDispatchedAction).to.eql(expectedDispatchedAction)
})

it ('dispatches the push action', () => {
const wrapper = shallow(<ConnectedSignIn />, {context: {store: mockStore}})
wrapper.props().push('/newLocation')
const actualDispatchedAction = mockStore.dispatch.args[0][0]
const expectedDispatchedAction = reactRouterReduxPush('/newLocation')
expect(actualDispatchedAction).to.eql(expectedDispatchedAction)
})

it ('dispatches the clearFlags action', () => {
const wrapper = shallow(<ConnectedSignIn />, {context: {store: mockStore}})
wrapper.props().clearFlags()
Expand Down
18 changes: 1 addition & 17 deletions client/modules/users/components/SignUp.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import React from 'react'
import {get, has, last} from 'lodash'
import {get, has} from 'lodash'
import {connect} from 'react-redux'
import {Link} from 'react-router-dom'
import {push} from 'react-router-redux'

import selectors from '../../../store/selectors'
import {signUp, clearFlags} from '../authReducer'
import userClientRole from '../../../lib/user-client-role'
import {showDialog, hideDialog} from '../../core/reducers/dialog'

import FieldGroup from '../../../components/form/FieldGroup'
Expand All @@ -17,7 +15,6 @@ import './signup.css'
class SignUp extends React.Component {
constructor(props) {
super(props)
this.redirectIfAlreadySignedIn(this.props)
this.state = {
firstName: {value: '', touched: false},
lastName: {value: '', touched: false},
Expand All @@ -31,13 +28,6 @@ class SignUp extends React.Component {
this.props.clearFlags()
}

redirectIfAlreadySignedIn(props) {
if (props.user && props.user._id) {
const role = userClientRole(props.user)
props.push(role ? `/${last(role.split('/'))}s` : '/')
}
}

onFieldChange = e => {
const {name, value} = e.target
this.setState({[name]: {value, touched: true}})
Expand All @@ -63,10 +53,6 @@ class SignUp extends React.Component {
window.location = `/api/auth/google?action=signup`
}

componentWillReceiveProps = nextProps => {
this.redirectIfAlreadySignedIn(nextProps)
}

isValid = field => has(
get(this.props, 'fetchUserError.paths', {}),
field
Expand Down Expand Up @@ -166,7 +152,6 @@ class SignUp extends React.Component {
}

const mapStateToProps = state => ({
user: selectors.auth.getUser(state),
fetchingUser: selectors.auth.fetching(state),
fetchUserError: selectors.auth.error(state),
googleAuthentication: get(selectors.settings.getSettings(state), 'googleAuthentication')
Expand All @@ -175,7 +160,6 @@ const mapStateToProps = state => ({
const mapDispatchToProps = dispatch => ({
signUp: user => dispatch(signUp(user)),
clearFlags: () => dispatch(clearFlags()),
push: location => dispatch(push(location)),
showDialog: dialogOptions => dispatch(showDialog(dialogOptions)),
hideDialog: () => dispatch(hideDialog())
})
Expand Down
5 changes: 4 additions & 1 deletion client/store/middleware/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import fetch from 'isomorphic-fetch'
import {normalize} from 'normalizr'
import {SubmissionError} from 'redux-form'

import {clearUser} from '../../modules/users/authReducer'

let _socket

const API_ROOT = process.env.NODE_ENV === 'production' ?
Expand Down Expand Up @@ -35,7 +37,7 @@ export function callApi(endpoint, method = 'GET', body, schema, responseSchema)
})
.then(response =>
response.json().then(json => {
if (!response.ok) return Promise.reject(json)
if (!response.ok) return Promise.reject({ ...json, status: response.status })
if (responseSchema) return normalize(json, responseSchema)
if (schema) return normalize(json, schema)
return json
Expand Down Expand Up @@ -93,6 +95,7 @@ export default socket => {
response
})),
error => {
if (error.status === 401) store.dispatch(clearUser())
next(actionWith({
type: failureType,
error
Expand Down
2 changes: 1 addition & 1 deletion client/store/middleware/api.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ describe('api middleware', function() {
expect(fetchMock).to.calledWith('root/foo')

return result.catch(err => {
expect(err).to.eql({error: 'error'})
expect(err).to.include({error: 'error'})
expect(responseMock.json).to.have.been.calledOnce
})
})
Expand Down
5 changes: 4 additions & 1 deletion server/routes/donation.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import Router from 'express-promise-router'
import donationController from '../controllers/donation'
import usersController from '../controllers/users'

This comment was marked as off-topic.

This comment was marked as off-topic.

This comment was marked as off-topic.


const {requiresLogin} = usersController

const donationRouter = Router({mergeParams: true})

Expand All @@ -10,6 +13,6 @@ donationRouter.route('/admin/donations/:donationId/approve')
.put(donationController.approve)

donationRouter.route('/donations/:donationId')
.put(donationController.hasAuthorization, donationController.sendEmail)
.put(requiresLogin, donationController.hasAuthorization, donationController.sendEmail)

export default donationRouter
Loading