├── .babelrc ├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── package.json └── src ├── authentication.js ├── index.js └── test ├── authentication-test.js └── init.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-airbnb", 3 | "env": { 4 | "browser": true, 5 | "mocha": true, 6 | "node": true 7 | }, 8 | "plugins": [ 9 | "react" 10 | ], 11 | "globals": { 12 | "expect": true 13 | }, 14 | "parser": "babel-eslint", 15 | "rules": { 16 | "semi": [1, "never"], 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | lib 29 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | example 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - stable 4 | - 4 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jackong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # redux-authentication 2 | 3 | [![NPM version][npm-image]][npm-url] 4 | [![build status][travis-image]][travis-url] 5 | [![David deps][david-image]][david-url] 6 | [![node version][node-image]][node-url] 7 | [![Gittip][gittip-image]][gittip-url] 8 | 9 | [npm-image]: https://img.shields.io/npm/v/redux-authentication.svg?style=flat-square 10 | [npm-url]: https://npmjs.org/package/redux-authentication 11 | [travis-image]: https://travis-ci.org/Jackong/redux-authentication.svg?branch=master 12 | [travis-url]: https://travis-ci.org/Jackong/redux-authentication 13 | [david-image]: https://img.shields.io/david/Jackong/redux-authentication.svg?style=flat-square 14 | [david-url]: https://david-dm.org/Jackong/redux-authentication 15 | [node-image]: https://img.shields.io/badge/node.js-%3E=_0.11-green.svg?style=flat-square 16 | [node-url]: http://nodejs.org/download/ 17 | [gittip-image]: https://img.shields.io/gratipay/Jackong.svg 18 | [gittip-url]: https://gratipay.com/~Jackong 19 | 20 | authentication component for redux 21 | 22 | # Install 23 | 24 | [![NPM](https://nodei.co/npm/redux-authentication.png?downloads=true)](https://nodei.co/npm/redux-authentication/) 25 | 26 | # Examples 27 | 28 | ```js 29 | import authentication from 'redux-authentication' 30 | import {connect} from 'react-redux' 31 | import React, { PropTypes } from 'react' 32 | import {goToLogin} from 'your-actions' 33 | 34 | @connect(state => ({ 35 | isAuthenticated: state.isAuthenticated 36 | }), { 37 | goToLogin,//map to props.goToLogin OR props.actions.goToLogin 38 | }) 39 | @authentication 40 | class App extends React.Component { 41 | // will be called until isAuthenticated is updated to true 42 | render() { 43 | return ( 44 |
45 | App 46 |
47 | ) 48 | } 49 | } 50 | 51 | export default App 52 | ``` 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-authentication", 3 | "version": "2.0.0", 4 | "description": "authentication component for redux", 5 | "main": "lib/index.js", 6 | "devDependencies": { 7 | "babel-cli": "^6.2.0", 8 | "babel-core": "^6.2.1", 9 | "babel-eslint": "^4.1.6", 10 | "babel-preset-es2015": "^6.1.18", 11 | "babel-preset-react": "^6.1.18", 12 | "chai": "^3.4.1", 13 | "enzyme": "^2.3.0", 14 | "eslint": "^1.10.1", 15 | "eslint-config-airbnb": "^1.0.2", 16 | "eslint-plugin-react": "^3.10.0", 17 | "jsdom": "^9.4.1", 18 | "mocha": "^2.3.4", 19 | "react": "^15.2.0", 20 | "react-addons-test-utils": "^15.2.0", 21 | "react-dom": "^15.2.0", 22 | "react-redux": "^4.0.0", 23 | "redux": "^3.0.4", 24 | "sinon": "^1.17.4" 25 | }, 26 | "scripts": { 27 | "test": "mocha --compilers js:babel-core/register --recursive --require src/test/init.js src/test/**/*-test.js", 28 | "test:watch": "mocha --compilers js:babel-core/register --recursive --require src/test/init.js -w src/test/**/*-test.js", 29 | "build": "babel src --out-dir lib", 30 | "clean": "rm -rf lib", 31 | "prepublish": "npm run clean && npm run build" 32 | }, 33 | "repository": { 34 | "type": "git", 35 | "url": "git+https://github.com/Jackong/redux-authentication.git" 36 | }, 37 | "keywords": [ 38 | "react", 39 | "redux", 40 | "authenticate", 41 | "authentication", 42 | "auth" 43 | ], 44 | "author": "jackong ", 45 | "license": "MIT", 46 | "bugs": { 47 | "url": "https://github.com/Jackong/redux-authentication/issues" 48 | }, 49 | "homepage": "https://github.com/Jackong/redux-authentication#readme", 50 | "dependencies": {} 51 | } 52 | -------------------------------------------------------------------------------- /src/authentication.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | 3 | const authentication = Component => { 4 | class Authentication extends React.Component { 5 | constructor(props) { 6 | super(props) 7 | this.isAuthenticating = false 8 | this.authenticate(this.props) 9 | } 10 | componentWillReceiveProps(nextProps) { 11 | this.authenticate(nextProps) 12 | } 13 | authenticate({ isAuthenticated, goToLogin, actions }) { 14 | if (isAuthenticated) { 15 | this.isAuthenticating = false 16 | return true 17 | } 18 | 19 | // prevent to call goToLogin multiple times 20 | if (this.isAuthenticating) { 21 | return false 22 | } 23 | this.isAuthenticating = true 24 | 25 | if (goToLogin) { 26 | goToLogin() 27 | return false 28 | } 29 | if (actions && actions.goToLogin) { 30 | actions.goToLogin() 31 | return false 32 | } 33 | return false 34 | } 35 | render() { 36 | if (!this.props.isAuthenticated) { 37 | return null 38 | } 39 | return ( 40 | 41 | ) 42 | } 43 | } 44 | Authentication.propTypes = { 45 | isAuthenticated: PropTypes.bool, 46 | } 47 | Authentication.displayName = `Authentication(${Component.displayName || Component.name})` 48 | return Authentication 49 | } 50 | 51 | export default authentication 52 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export { default as default} from './authentication' 2 | -------------------------------------------------------------------------------- /src/test/authentication-test.js: -------------------------------------------------------------------------------- 1 | import sinon from 'sinon' 2 | import React from 'react' 3 | import { mount } from 'enzyme' 4 | 5 | import authentication from '../authentication' 6 | 7 | class App extends React.Component { 8 | componentWillMount() { 9 | } 10 | componentDidMount() { 11 | } 12 | componentWillReceiveProps() { 13 | } 14 | render() { 15 | return ( 16 |
17 | App 18 |
19 | ) 20 | } 21 | } 22 | const AppWithAuth = authentication(App) 23 | 24 | describe('Authentication', () => { 25 | beforeEach(() => { 26 | sinon.restore() 27 | }) 28 | 29 | describe('with authentication flow', () => { 30 | it('should be ok', () => { 31 | const goToLogin = sinon.spy() 32 | const cwm = sinon.spy(App.prototype, 'componentWillMount') 33 | const cdm = sinon.spy(App.prototype, 'componentDidMount') 34 | const cwr = sinon.spy(App.prototype, 'componentWillReceiveProps') 35 | const render = sinon.spy(App.prototype, 'render') 36 | const authenticate = sinon.spy(AppWithAuth.prototype, 'authenticate') 37 | 38 | 39 | // from unauthenticated status 40 | const wrapper = mount( 41 | 42 | ) 43 | 44 | expect(cwm.callCount).to.be.equal(0) 45 | expect(cdm.callCount).to.be.equal(0) 46 | expect(cwr.callCount).to.be.equal(0) 47 | expect(render.callCount).to.be.equal(0) 48 | expect(authenticate.callCount).to.be.equal(1) 49 | 50 | expect(goToLogin.callCount).to.be.equal(1) 51 | 52 | // to authenticated status 53 | wrapper.setProps({ isAuthenticated: true }) 54 | 55 | expect(cwm.callCount).to.be.equal(1) 56 | expect(cdm.callCount).to.be.equal(1) 57 | expect(cwr.callCount).to.be.equal(0) 58 | expect(render.callCount).to.be.equal(1) 59 | expect(authenticate.callCount).to.be.equal(2) 60 | 61 | // keeping called once 62 | expect(goToLogin.callCount).to.be.equal(1) 63 | 64 | // set others props 65 | wrapper.setProps({ user: {} }) 66 | expect(cwm.callCount).to.be.equal(1) 67 | expect(cdm.callCount).to.be.equal(1) 68 | expect(cwr.callCount).to.be.equal(1) 69 | 70 | // will re-render 71 | expect(render.callCount).to.be.equal(2) 72 | 73 | // should call authenticate to check 74 | expect(authenticate.callCount).to.be.equal(3) 75 | 76 | // and back to unauthenticated status 77 | wrapper.setProps({ isAuthenticated: false }) 78 | 79 | expect(cwm.callCount).to.be.equal(1) 80 | expect(cdm.callCount).to.be.equal(1) 81 | expect(cwr.callCount).to.be.equal(1) 82 | expect(render.callCount).to.be.equal(2) 83 | 84 | // should call authenticate 85 | expect(authenticate.callCount).to.be.equal(4) 86 | 87 | // should go to login again 88 | expect(goToLogin.callCount).to.be.equal(2) 89 | }) 90 | }) 91 | }) 92 | -------------------------------------------------------------------------------- /src/test/init.js: -------------------------------------------------------------------------------- 1 | import {expect} from 'chai' 2 | import { jsdom } from 'jsdom' 3 | 4 | global.expect = expect 5 | 6 | const exposedProperties = ['window', 'navigator', 'document'] 7 | 8 | global.document = jsdom('') 9 | global.window = document.defaultView 10 | Object.keys(document.defaultView).forEach((property) => { 11 | if (typeof global[property] === 'undefined') { 12 | exposedProperties.push(property) 13 | global[property] = document.defaultView[property] 14 | } 15 | }) 16 | 17 | global.navigator = { 18 | userAgent: 'node.js', 19 | } 20 | --------------------------------------------------------------------------------