Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds onEnter function to UserAuthWrapper #19

Merged
merged 2 commits into from
Feb 15, 2016
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
34 changes: 33 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,14 @@ It will display in React-devtools. Defaults to `UserAuthWrapper`
If it evaluates to false the browser will be redirected to `failureRedirectPath`, otherwise `DecoratedComponent` will be rendered.
* `[allowRedirect]` \(*Bool*): Optional bool on whether to pass a `redirect` query parameter to the `failureRedirectPath`

#### Returns
After applying the configObject, `UserAuthWrapper` returns a function which can applied to a Component to wrap in authentication and
authorization checks. The function also has the following extra properties:
* `onEnter(store, nextState, replace)` \(*Function*): Function to be optionally used in the [onEnter](https://github.com/reactjs/react-router/blob/master/docs/API.md#onenternextstate-replace-callback) property of a route.

#### Component Parameter
* `DecoratedComponent` \(*React Component*): The component to be wrapped in the auth check. It will pass down all props given to the returned component as well as the prop `authData` which is the result of the `authSelector`
* `DecoratedComponent` \(*React Component*): The component to be wrapped in the auth check. It will pass down all props given to the returned component as well as the prop `authData` which is the result of the `authSelector`.
The component is not modified and all static properties are hoisted to the returned component

## Authorization & More Advanced Usage

Expand Down Expand Up @@ -171,6 +177,32 @@ class MyComponents extends Component {
}
```

### Server Side Rendering
In order to perform authentication and authorization checks for Server Side Rendering, you may need to use the `onEnter` property
of a `<Route>`. You can access the `onEnter` method of the UserAuthWrapper after applying the config parameters:
```js
import { UserAuthWrapper } from 'redux-auth-wrapper';

const UserIsAuthenticated = UserAuthWrapper({
authSelector: state => state.user,
redirectAction: routeActions.replace,
wrapperDisplayName: 'UserIsAuthenticated'
})

const getRoutes = (store) => {
const connect = (fn) => (nextState, replaceState) => fn(store, nextState, replaceState);

return (
<Route>
<Route path="/" component={App}>
<Route path="login" component={Login}/>
<Route path="foo" component={UserIsAuthenticated(Foo)} onEnter={connect(UserIsAuthenticated.onEnter)} />
</Route>
</Route>
);
};
```

### Other examples
* [Redux-Router and React-Router 1.0 with JWT](https://github.com/mjrussell/react-redux-jwt-auth-example/tree/auth-wrapper)
* [React-Router-Redux and React-Router 2.0 with JWT](https://github.com/mjrussell/react-redux-jwt-auth-example/tree/react-router-redux)
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"react": "~0.14.3",
"react-addons-test-utils": "^0.14.6",
"react-redux": "^4.0.1",
"react-router": "2.0.0-rc5",
"react-router": "2.0.0",
"react-router-redux": "~3.0.0",
"redux": "^3.2.0"
},
Expand Down
59 changes: 33 additions & 26 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,27 @@ const UserAuthWrapper = (args) => {
...defaults,
...args
}

const isAuthorized = (authData) => predicate(authData)

const ensureAuth = ({ location, authData }, redirect) => {
let query
if (allowRedirectBack) {
query = { redirect: `${location.pathname}${location.search}` }
} else {
query = {}
}

if (!isAuthorized(authData)) {
redirect({
pathname: failureRedirectPath,
query
})
}
}

// Wraps the component that needs the auth enforcement
return function wrapComponent(DecoratedComponent) {
function wrapComponent(DecoratedComponent) {
const displayName = DecoratedComponent.displayName || DecoratedComponent.name || 'Component'

const mapDispatchToProps = (dispatch) => {
Expand Down Expand Up @@ -51,16 +70,16 @@ const UserAuthWrapper = (args) => {
};

componentWillMount() {
this.ensureAuth(this.props)
ensureAuth(this.props, this.getRedirectFunc(this.props))
}

componentWillReceiveProps(nextProps) {
this.ensureAuth(nextProps)
ensureAuth(nextProps, this.getRedirectFunc(nextProps))
}

getRedirectFunc = () => {
if (this.props.redirect) {
return this.props.redirect
getRedirectFunc = (props) => {
if (props.redirect) {
return props.redirect
} else {
if (!this.context.router.replace) {
/* istanbul ignore next sanity */
Expand All @@ -71,31 +90,12 @@ const UserAuthWrapper = (args) => {
}
};

isAuthorized = (authData) => predicate(authData);

ensureAuth = (props) => {
const { location, authData } = props
let query
if (allowRedirectBack) {
query = { redirect: `${location.pathname}${location.search}` }
} else {
query = {}
}

if (!this.isAuthorized(authData)) {
this.getRedirectFunc()({
pathname: failureRedirectPath,
query
})
}
};

render() {
// Allow everything but the replace aciton creator to be passed down
// Includes route props from React-Router and authData
const { redirect, authData, ...otherProps } = this.props

if (this.isAuthorized(authData)) {
if (isAuthorized(authData)) {
return <DecoratedComponent authData={authData} {...otherProps} />
} else {
// Don't need to display anything because the user will be redirected
Expand All @@ -106,6 +106,13 @@ const UserAuthWrapper = (args) => {

return hoistStatics(UserAuthWrapper, DecoratedComponent)
}

wrapComponent.onEnter = (store, nextState, replace) => {
const authData = authSelector(store.getState())
ensureAuth({ location: nextState.location, authData }, replace)
}

return wrapComponent
}

// Support the old 0.1.x with deprecation warning
Expand Down
25 changes: 23 additions & 2 deletions test/UserAuthWrapper-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ class UnprotectedParentComponent extends Component {
}
}

const routes = (
const defaultRoutes = (
<Route path="/" component={App} >
<Route path="login" component={UnprotectedComponent} />
<Route path="auth" component={UserIsAuthenticated(UnprotectedComponent)} />
Expand All @@ -135,7 +135,7 @@ const userLoggedIn = (firstName = 'Test', lastName = 'McDuderson') => {
}
}

const setupTest = () => {
const setupTest = (routes = defaultRoutes) => {
const history = createMemoryHistory()
const store = configureStore(history)

Expand Down Expand Up @@ -299,4 +299,25 @@ describe('UserAuthWrapper', () => {
expect(authed.staticFun).to.be.a('function')
expect(authed.staticFun()).to.equal('auth')
})

it('provides an onEnter static function', () => {
let store
const connect = (fn) => (nextState, replaceState) => fn(store, nextState, replaceState)

const routesOnEnter = (
<Route path="/" component={App} >
<Route path="login" component={UnprotectedComponent} />
<Route path="onEnter" component={UnprotectedComponent} onEnter={connect(UserIsAuthenticated.onEnter)} />
</Route>
)

const { history, store: createdStore } = setupTest(routesOnEnter)
store = createdStore

expect(store.getState().routing.location.pathname).to.equal('/')
expect(store.getState().routing.location.search).to.equal('')
history.push('/onEnter')
expect(store.getState().routing.location.pathname).to.equal('/login')
expect(store.getState().routing.location.search).to.equal('?redirect=%2FonEnter')
})
})