├── .babelrc
├── .eslintrc.yaml
├── .gitignore
├── .travis.yml
├── Gruntfile.js
├── README.md
├── eslint
├── eslint-defaults.yaml
├── eslint-es2015.yaml
├── eslint-jsx.yaml
└── eslint-node-commonjs.yaml
├── examples
├── .eslintrc.yaml
├── bocoup
│ ├── app.jsx
│ ├── auth.js
│ └── index.html
├── index.html
├── server.js
└── webpack.config.babel.js
├── package.json
├── src
├── .eslintrc-test.yaml
├── .eslintrc.yaml
├── auth.js
├── auth.test.js
├── bind-methods.js
├── bind-methods.test.js
└── index.js
└── tools
├── .eslintrc.yaml
├── gruntfile.js
└── test-globals.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["transform-runtime"],
3 | "presets": ["es2015"]
4 | }
5 |
--------------------------------------------------------------------------------
/.eslintrc.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | extends:
3 | - "./eslint/eslint-defaults.yaml"
4 | - "./eslint/eslint-node-commonjs.yaml"
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | build
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "4.2"
4 | sudo: false
5 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('babel-register');
4 |
5 | module.exports = function(grunt) {
6 | module.exports.grunt = grunt;
7 | require('./tools/gruntfile');
8 | };
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-router-oauth
2 |
3 | > Basic oauth support for react-router
4 |
5 | [](https://travis-ci.org/bocoup/react-router-oauth)
6 | [](http://gruntjs.com/)
7 |
8 | _Work in progress!_
9 |
10 | ## Examples
11 |
12 | 1. Clone this repo
13 | 1. Install project dependencies with `npm install`
14 | 1. Start the development web server with `npm start`
15 | 1. Browse to
16 |
--------------------------------------------------------------------------------
/eslint/eslint-defaults.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | rules:
3 |
4 | # Possible Errors
5 |
6 | comma-dangle:
7 | - 2
8 | - always-multiline
9 | no-cond-assign:
10 | - 2
11 | - except-parens
12 | no-console: 0
13 | no-constant-condition: 1
14 | no-control-regex: 2
15 | no-debugger: 1
16 | no-dupe-args: 2
17 | no-dupe-keys: 2
18 | no-duplicate-case: 2
19 | no-empty-character-class: 2
20 | no-empty: 1
21 | no-ex-assign: 2
22 | no-extra-boolean-cast: 2
23 | no-extra-parens:
24 | - 2
25 | - functions
26 | no-extra-semi: 2
27 | no-func-assign: 2
28 | no-inner-declarations: 2
29 | no-invalid-regexp: 2
30 | no-irregular-whitespace: 2
31 | no-negated-in-lhs: 2
32 | no-obj-calls: 2
33 | no-regex-spaces: 2
34 | no-sparse-arrays: 2
35 | no-unreachable: 1
36 | use-isnan: 2
37 | valid-jsdoc:
38 | - 2
39 | - prefer:
40 | return: returns
41 | valid-typeof: 2
42 | no-unexpected-multiline: 2
43 |
44 | # Best Practices
45 |
46 | accessor-pairs: 2
47 | block-scoped-var: 2
48 | complexity: 0
49 | consistent-return: 2
50 | curly:
51 | - 2
52 | - all
53 | default-case: 2
54 | dot-notation: 2
55 | dot-location:
56 | - 2
57 | - property
58 | eqeqeq:
59 | - 2
60 | - "allow-null"
61 | guard-for-in: 0
62 | no-alert: 2
63 | no-caller: 2
64 | no-div-regex: 0
65 | no-else-return: 2
66 | no-empty-label: 2
67 | no-eq-null: 0
68 | no-eval: 2
69 | no-extend-native: 0
70 | no-extra-bind: 2
71 | no-fallthrough: 2
72 | no-floating-decimal: 2
73 | no-implicit-coercion: 2
74 | no-implied-eval: 2
75 | no-invalid-this: 0
76 | no-iterator: 2
77 | no-labels: 0
78 | no-lone-blocks: 2
79 | no-loop-func: 2
80 | no-multi-spaces: 2
81 | no-multi-str: 2
82 | no-native-reassign: 2
83 | no-new-func: 0
84 | no-new-wrappers: 2
85 | no-new: 2
86 | no-octal-escape: 2
87 | no-octal: 2
88 | no-param-reassign: 0
89 | no-process-env: 0
90 | no-proto: 2
91 | no-redeclare:
92 | - 2
93 | - builtinGlobals: false
94 | no-return-assign: 2
95 | no-script-url: 2
96 | no-self-compare: 2
97 | no-sequences: 2
98 | no-throw-literal: 2
99 | no-unused-expressions: 2
100 | no-useless-call: 2
101 | no-void: 2
102 | no-warning-comments: 1
103 | no-with: 2
104 | radix: 2
105 | vars-on-top: 0
106 | wrap-iife:
107 | - 2
108 | - inside
109 | yoda:
110 | - 2
111 | - never
112 |
113 | # Strict Mode
114 |
115 | strict:
116 | - 2
117 | - function
118 |
119 | # Variables
120 |
121 | init-declarations: 0
122 | no-catch-shadow: 0
123 | no-delete-var: 2
124 | no-label-var: 2
125 | no-shadow-restricted-names: 2
126 | no-shadow: 2
127 | no-undef-init: 2
128 | no-undef: 2
129 | no-undefined: 2
130 | no-unused-vars:
131 | - 2
132 | - vars: all
133 | args: none
134 | no-use-before-define: 2
135 |
136 | # Stylistic Issues
137 |
138 | array-bracket-spacing:
139 | - 2
140 | - never
141 | brace-style:
142 | - 2
143 | - stroustrup
144 | - allowSingleLine: true
145 | camelcase:
146 | - 2
147 | - properties: never
148 | comma-spacing:
149 | - 2
150 | - before: false
151 | after: true
152 | comma-style:
153 | - 2
154 | - last
155 | computed-property-spacing:
156 | - 2
157 | - never
158 | consistent-this:
159 | - 2
160 | - that
161 | eol-last: 0
162 | func-names: 0
163 | func-style: 0
164 | id-length: 0
165 | id-match: 0
166 | indent:
167 | - 2
168 | - 2
169 | - SwitchCase: 1
170 | VariableDeclarator: 2
171 | key-spacing:
172 | - 2
173 | - beforeColon: false
174 | afterColon: true
175 | lines-around-comment:
176 | - 2
177 | - beforeBlockComment: true
178 | linebreak-style:
179 | - 2
180 | - unix
181 | max-len:
182 | - 1
183 | - 120
184 | - 4
185 | max-nested-callbacks: 0
186 | new-cap:
187 | - 2
188 | - newIsCap: true
189 | capIsNew: true
190 | new-parens: 0
191 | newline-after-var: 0
192 | no-array-constructor: 2
193 | no-continue: 0
194 | no-inline-comments: 0
195 | no-lonely-if: 2
196 | no-mixed-spaces-and-tabs: 2
197 | no-multiple-empty-lines:
198 | - 2
199 | - max: 2
200 | no-nested-ternary: 0
201 | no-new-object: 2
202 | no-spaced-func: 2
203 | no-ternary: 0
204 | no-trailing-spaces: 2
205 | no-underscore-dangle: 0
206 | no-unneeded-ternary: 2
207 | object-curly-spacing:
208 | - 2
209 | - never
210 | one-var:
211 | - 2
212 | - uninitialized: always
213 | initialized: never
214 | operator-assignment:
215 | - 2
216 | - always
217 | operator-linebreak:
218 | - 2
219 | - after
220 | padded-blocks: 0
221 | quote-props:
222 | - 2
223 | - as-needed
224 | - {keywords: false}
225 | quotes:
226 | - 2
227 | - single
228 | - avoid-escape
229 | semi-spacing:
230 | - 2
231 | - before: false
232 | after: true
233 | semi:
234 | - 2
235 | - always
236 | sort-vars: 0
237 | space-after-keywords:
238 | - 2
239 | - always
240 | space-before-blocks:
241 | - 2
242 | - always
243 | space-before-function-paren:
244 | - 2
245 | - never
246 | space-in-parens:
247 | - 2
248 | - never
249 | space-infix-ops:
250 | - 2
251 | - int32Hint: false
252 | space-return-throw-case: 2
253 | space-unary-ops:
254 | - 2
255 | - words: true
256 | nonwords: false
257 | spaced-comment:
258 | - 2
259 | - always
260 | wrap-regex: 0
261 |
--------------------------------------------------------------------------------
/eslint/eslint-es2015.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | ecmaFeatures:
3 | modules: true
4 |
5 | env:
6 | es6: true
7 |
8 | rules:
9 | # General but applicable here
10 |
11 | no-inner-declarations: 0
12 | no-iterator: 0
13 |
14 | # ECMAScript 6
15 |
16 | arrow-parens:
17 | - 2
18 | - as-needed
19 | arrow-spacing:
20 | - 2
21 | - before: true
22 | after: true
23 | constructor-super: 2
24 | generator-star-spacing:
25 | - 2
26 | - before
27 | no-class-assign: 2
28 | no-const-assign: 2
29 | no-this-before-super: 2
30 | no-var: 2
31 | object-shorthand:
32 | - 2
33 | - always
34 | prefer-const: 2
35 | prefer-spread: 2
36 | prefer-reflect: 0
37 | require-yield: 2
38 |
--------------------------------------------------------------------------------
/eslint/eslint-jsx.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | plugins:
3 | - react
4 |
5 | ecmaFeatures:
6 | jsx: true
7 |
8 | rules:
9 | react/display-name: 0
10 | react/jsx-boolean-value:
11 | - 2
12 | - always
13 | react/jsx-closing-bracket-location: 0
14 | react/jsx-curly-spacing:
15 | - 2
16 | - never
17 | react/jsx-indent-props:
18 | - 2
19 | - 2
20 | react/jsx-max-props-per-line:
21 | - 1
22 | -
23 | maximum: 3
24 | react/jsx-no-duplicate-props:
25 | - 2
26 | - ignoreCase: false
27 | react/jsx-no-literals: 0
28 | react/jsx-no-undef: 2
29 | jsx-quotes:
30 | - 2
31 | - prefer-double
32 | react/jsx-sort-prop-types: 0
33 | react/jsx-sort-props: 0
34 | react/jsx-uses-react: 2
35 | react/jsx-uses-vars: 2
36 | react/no-danger: 2
37 | react/no-did-mount-set-state:
38 | - 2
39 | - allow-in-func
40 | react/no-did-update-set-state: 2
41 | react/no-multi-comp: 0
42 | react/no-set-state: 0
43 | react/no-unknown-property: 2
44 | react/prop-types: 1
45 | react/react-in-jsx-scope: 2
46 | react/require-extension:
47 | - 2
48 | - extensions:
49 | - ".js"
50 | - ".jsx"
51 | react/self-closing-comp: 2
52 | react/sort-comp: 2
53 | react/wrap-multilines: 2
54 |
--------------------------------------------------------------------------------
/eslint/eslint-node-commonjs.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | env:
3 | node: true
4 |
5 | ecmaFeatures:
6 | blockBindings: true
7 |
8 | rules:
9 |
10 | # General but applicable here
11 |
12 | strict:
13 | - 2
14 | - global
15 |
16 | # Node.js and CommonJS
17 |
18 | callback-return:
19 | - 2
20 | - [ callback, cb, done, next ]
21 | handle-callback-err:
22 | - 2
23 | - "^err(?:or)?$"
24 | no-mixed-requires: 0
25 | no-new-require: 2
26 | no-path-concat: 2
27 | no-process-exit: 2
28 | no-restricted-modules: 0
29 | no-sync: 0
30 |
--------------------------------------------------------------------------------
/examples/.eslintrc.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | env:
3 | browser: true
4 | extends:
5 | - "../eslint/eslint-defaults.yaml"
6 | - "../eslint/eslint-es2015.yaml"
7 | - "../eslint/eslint-jsx.yaml"
8 |
--------------------------------------------------------------------------------
/examples/bocoup/app.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {render} from 'react-dom';
3 | import {Router, Route, IndexRoute, Link, browserHistory} from 'react-router';
4 |
5 | import auth from './auth';
6 |
7 | const Login = ({location: {state}}) => {
8 | const redirectTo = state && state.nextPathname;
9 | return (
10 |
11 |
You must log in to use this app!
12 |
Log In
13 |
14 | );
15 | };
16 |
17 | const App = ({children}) => (
18 |
19 |
Bocoup API Auth Example
20 |
21 |
Content
22 | {children}
23 |
24 | );
25 |
26 | const AuthStatus = () => (
27 |
28 | You are currently logged in. Log out
29 |
30 | );
31 |
32 | const Index = () => (
33 |
34 |
This is the index page.
35 |
You should be able to log out from this page, and then log back in.
36 |
Visit the subpage to test deep-link
37 | redirection and handling API errors.
38 |
39 | );
40 |
41 | function simulateApiError(event) {
42 | event.preventDefault();
43 | // This would normally be done in the error callback of an API Ajax request:
44 | auth.logout({redirectBack: true});
45 | // const {token} = auth.getCredentials();
46 | // request
47 | // .get('/some-request')
48 | // .set('Authorization', `Bearer ${token}`)
49 | // .then(({body}) => {
50 | // console.log(body);
51 | // }, err => {
52 | // if (err.status === 401) {
53 | // auth.logout({redirectBack: true});
54 | // }
55 | // throw err;
56 | // });
57 | }
58 |
59 | const SubPage = () => (
60 |
61 |
This is the subpage.
62 |
Deep-link Redirection
63 |
64 | - Note the URL to this page, and copy it to the clipboard.
65 | - Click the "Log out" link.
66 | - Paste the URL you copied into the address bar. You should be redirected to the login page.
67 | - Click the "Log in" link.
68 | - You should be redirected to GitHub*, then back to the original page, with the query string intact.
69 |
70 |
Handling API 401 Errors
71 |
72 | - Note the URL to this page.
73 | - Click Simulate an API error.
74 | - You should be redirected to the login page.
75 | - Click the "Log in" link.
76 | - You should be redirected to GitHub*, then back to the original page, with the query string intact.
77 |
78 |
* Unless the Bocoup API remembers you from a previous login.
79 |
Return to the index.
80 |
81 | );
82 |
83 | const routes = (
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 | );
92 |
93 | render(routes, document.getElementById('root'));
94 |
--------------------------------------------------------------------------------
/examples/bocoup/auth.js:
--------------------------------------------------------------------------------
1 | import queryString from 'query-string';
2 | import {browserHistory} from 'react-router';
3 | import Auth from 'react-router-oauth';
4 |
5 | // A few app defaults.
6 | const loginRoute = '/login';
7 | const loggedInRoute = '/';
8 |
9 | // Not actually used by the auth system, but it's related.
10 | function authUrl(redirectTo) {
11 | const API_AUTH_URL = 'https://api.bocoup.com/v3/auth/authenticate';
12 | const provider = 'github';
13 | const referer = this.getBaseUrl(redirectTo);
14 | const search = queryString.stringify({provider, referer});
15 | return `${API_AUTH_URL}?${search}`;
16 | }
17 |
18 | // If auth is required for the current route, but the user is not already
19 | // authed, this function will be called. If it returns an object, that object
20 | // will be used to log the user in.
21 | function parseCredentials() {
22 | // Get auth params from the query string.
23 | const params = queryString.parse(location.search);
24 | const {access_token: token, id} = params;
25 | if (token && id) {
26 | // Remove related params from the query object.
27 | delete params.access_token;
28 | delete params.id;
29 | // Replace the current page with the same page, minus the auth query params.
30 | let search = queryString.stringify(params);
31 | if (search) { search = `?${search}`; }
32 | history.replaceState('', {}, `${location.pathname}${search}`);
33 | // The returned object will be used to login.
34 | return {token, id};
35 | }
36 | }
37 |
38 | export default new Auth({
39 | browserHistory,
40 | loginRoute,
41 | loggedInRoute,
42 | authUrl,
43 | parseCredentials,
44 | });
45 |
--------------------------------------------------------------------------------
/examples/bocoup/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Bocoup API Auth Example
5 |
6 |
7 |
8 | « Return to Examples Index
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Examples
5 |
6 |
7 | Examples
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/examples/server.js:
--------------------------------------------------------------------------------
1 | // Based on the examples in https://github.com/rackt/react-router
2 |
3 | import fs from 'fs';
4 | import path from 'path';
5 | import express from 'express';
6 | import rewrite from 'express-urlrewrite';
7 | import webpack from 'webpack';
8 | import webpackDevMiddleware from 'webpack-dev-middleware';
9 | import webpackConfig from './webpack.config.babel';
10 |
11 | const app = express();
12 | const compiler = webpack(webpackConfig);
13 |
14 | app.use(webpackDevMiddleware(compiler, {
15 | publicPath: '/__build__/',
16 | stats: {
17 | colors: true,
18 | },
19 | }));
20 |
21 | fs.readdirSync(__dirname).forEach(file => {
22 | const stat = fs.statSync(path.join(__dirname, file));
23 | if (stat.isDirectory()) {
24 | app.use(rewrite(`/${file}/*`, `/${file}/index.html`));
25 | }
26 | });
27 |
28 | app.use(express.static(__dirname));
29 |
30 | app.listen(8080, function() {
31 | console.log('Server listening on http://localhost:8080, Ctrl+C to stop.');
32 | });
33 |
--------------------------------------------------------------------------------
/examples/webpack.config.babel.js:
--------------------------------------------------------------------------------
1 | // Based on the examples in https://github.com/rackt/react-router
2 |
3 | import fs from 'fs';
4 | import path from 'path';
5 | import webpack from 'webpack';
6 |
7 | const getPath = (...args) => path.join(__dirname, ...args);
8 |
9 | export default {
10 | devtool: 'cheap-module-eval-source-map',
11 |
12 | resolve: {
13 | extensions: ['', '.js', '.jsx'],
14 | alias: {
15 | 'react-router-oauth': getPath('../build'),
16 | },
17 | },
18 |
19 | entry: fs.readdirSync(getPath()).reduce((entries, dir) => {
20 | const stat = fs.statSync(getPath(dir));
21 | if (stat.isDirectory()) {
22 | entries[dir] = getPath(dir, 'app');
23 | }
24 | return entries;
25 | }, {}),
26 |
27 | output: {
28 | path: getPath('__build__'),
29 | filename: '[name].js',
30 | chunkFilename: '[id].chunk.js',
31 | publicPath: '/__build__/',
32 | },
33 |
34 | module: {
35 | preLoaders: [
36 | {
37 | test: /\.jsx?$/,
38 | loader: 'eslint-loader',
39 | include: getPath(),
40 | },
41 | ],
42 | loaders: [
43 | {
44 | test: /\.jsx?$/,
45 | loader: 'babel',
46 | include: getPath(),
47 | query: {
48 | presets: ['react', 'es2015'],
49 | },
50 | },
51 | ],
52 | },
53 |
54 | plugins: [
55 | new webpack.optimize.CommonsChunkPlugin('shared.js'),
56 | ],
57 | };
58 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-router-oauth",
3 | "description": "Basic oauth support for react-router",
4 | "version": "0.2.0",
5 | "dependencies": {
6 | "babel-plugin-transform-runtime": "^6.4.0",
7 | "babel-preset-es2015": "^6.3.13",
8 | "babel-runtime": "^6.5.0"
9 | },
10 | "main": "build/index",
11 | "files": [
12 | "build"
13 | ],
14 | "scripts": {
15 | "prepublish": "grunt test build",
16 | "build": "grunt build",
17 | "start": "babel-node examples/server",
18 | "test": "grunt test"
19 | },
20 | "devDependencies": {
21 | "babel": "^6.5.1",
22 | "babel-cli": "^6.5.1",
23 | "babel-core": "^6.5.1",
24 | "babel-loader": "^6.2.2",
25 | "babel-preset-es2015": "^6.5.0",
26 | "babel-preset-react": "^6.5.0",
27 | "babel-register": "^6.4.3",
28 | "chai": "^3.4.1",
29 | "eslint-loader": "^1.2.1",
30 | "eslint-plugin-react": "^3.16.1",
31 | "express": "^4.13.4",
32 | "express-urlrewrite": "^1.2.0",
33 | "grunt": "^0.4.5",
34 | "grunt-babel": "^6.0.0",
35 | "grunt-cli": "^0.1.13",
36 | "grunt-contrib-clean": "^0.7.0",
37 | "grunt-contrib-watch": "^0.6.1",
38 | "grunt-eslint": "^17.3.1",
39 | "grunt-mocha-test": "^0.12.7",
40 | "mocha": "^2.3.4",
41 | "react": "^0.14.7",
42 | "react-dom": "^0.14.7",
43 | "react-router": "^2.0.0",
44 | "rewrite": "0.0.1",
45 | "webpack": "^1.12.13",
46 | "webpack-dev-middleware": "^1.5.1"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/.eslintrc-test.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | env:
3 | mocha: true
4 | globals:
5 | assert: true
6 | expect: true
7 | extends:
8 | - ".eslintrc.yaml"
9 |
--------------------------------------------------------------------------------
/src/.eslintrc.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | env:
3 | browser: true
4 | extends:
5 | - "../eslint/eslint-defaults.yaml"
6 | - "../eslint/eslint-es2015.yaml"
7 |
--------------------------------------------------------------------------------
/src/auth.js:
--------------------------------------------------------------------------------
1 | import bindMethods from './bind-methods';
2 |
3 | export default class Auth {
4 | constructor({
5 | browserHistory,
6 | key = 'credentials',
7 | loginRoute = '/login',
8 | loggedInRoute = '/',
9 | authUrl,
10 | parseCredentials,
11 | }) {
12 | this.browserHistory = browserHistory;
13 | this.key = key;
14 | this.loginRoute = loginRoute;
15 | this.loggedInRoute = loggedInRoute;
16 | this.authUrl = authUrl;
17 | this.parseCredentials = parseCredentials;
18 | this.baseHref = null;
19 | // Simplify passing methods around.
20 | bindMethods(this);
21 | }
22 |
23 | // Store credentials for later retrieval.
24 | login(credentials) {
25 | if (!credentials) {
26 | throw new TypeError('Missing login credentials.');
27 | }
28 | const json = JSON.stringify(credentials);
29 | localStorage.setItem(this.key, json);
30 | }
31 |
32 | // Remove stored credentials. If `redirectBack` is true, logging in will
33 | // redirect back to the current route instead of the default `loggedInRoute`.
34 | // (use in cases where the user gets automatically logged out)
35 | logout({redirectBack} = {}) {
36 | localStorage.removeItem(this.key);
37 | // Redirect to the login page.
38 | if (this.browserHistory) {
39 | const nextPathname = redirectBack ? this.getRoutePath() : this.loggedInRoute;
40 | this.redirectToLogin({nextPathname});
41 | }
42 | }
43 |
44 | // Get stored credentials. If none are found, attempt to parse them from
45 | // the current environment, and then store them if found. If "exists" is
46 | // true, return a Boolean value based on whether or not they are stored.
47 | getCredentials({exists} = {}) {
48 | let json = localStorage.getItem(this.key);
49 | if (!json) {
50 | const credentials = this.parseCredentials();
51 | if (credentials) {
52 | this.login(credentials);
53 | }
54 | json = localStorage.getItem(this.key);
55 | }
56 | return exists ? Boolean(json) : json ? JSON.parse(json) : {};
57 | }
58 |
59 | // Do stored credentials exist? If none are found, attempt to parse them
60 | // from the current environment, and then store them if found.
61 | isLoggedIn() {
62 | return this.getCredentials({exists: true});
63 | }
64 |
65 | // Pass in a location object, and get back path + querystring as a string.
66 | // Don't pass in anything to use the current page's location object.
67 | getRoutePath(_location) {
68 | let pathname, search;
69 | if (_location) {
70 | ({pathname, search} = _location);
71 | }
72 | else {
73 | ({pathname, search} = location);
74 | pathname = pathname.slice(this.getBasePath().length);
75 | }
76 | return `${pathname}${search}`;
77 | }
78 |
79 | // Get relative "base" path for the current app.
80 | getBasePath() {
81 | if (this.baseHref === null) {
82 | const elem = document.getElementsByTagName('base')[0];
83 | const href = elem && elem.getAttribute('href') || '';
84 | this.baseHref = href.replace(/\/$/, '');
85 | }
86 | return this.baseHref;
87 | }
88 |
89 | // Get absolute "base" path for the current app.
90 | getBaseUrl(path) {
91 | if (!path) {
92 | path = this.loggedInRoute;
93 | }
94 | const baseHref = this.getBasePath();
95 | return `${location.protocol}//${location.host}${baseHref}${path}`;
96 | }
97 |
98 | // Redirect to the login page. If `replace` is specified, that replace
99 | // function will be used, otherwise the specified `browserHistory` will be,
100 | // otherwise nothing will happen. `nextPathname` is the URL to redirect to
101 | // after successful login.
102 | redirectToLogin({replace, nextPathname}) {
103 | if (!replace) {
104 | replace = this.browserHistory && this.browserHistory.replace;
105 | }
106 | if (replace) {
107 | replace({
108 | pathname: this.loginRoute,
109 | state: {nextPathname},
110 | });
111 | }
112 | }
113 |
114 | // React-router onEnter handler. If the given route (or any child route) is
115 | // not authed, redirect to the specified `loginRoute`
116 | requireAuth(nextState, replace) {
117 | if (!this.isLoggedIn()) {
118 | const nextPathname = this.getRoutePath(nextState.location);
119 | this.redirectToLogin({replace, nextPathname});
120 | }
121 | }
122 |
123 | // React-router onEnter handler. If the given route (or any child route) is
124 | // authed, redirect to the specified `loggedInRoute`
125 | requireNoAuth(nextState, replace) {
126 | if (this.isLoggedIn()) {
127 | replace(this.loggedInRoute);
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/src/auth.test.js:
--------------------------------------------------------------------------------
1 | import Auth from './auth';
2 |
3 | describe('auth', function() {
4 |
5 | it('should export a constructor function', function() {
6 | expect(Auth).to.be.a('function');
7 | });
8 |
9 | });
10 |
--------------------------------------------------------------------------------
/src/bind-methods.js:
--------------------------------------------------------------------------------
1 | export default function(obj, methodNames) {
2 | const protoObj = Object.getPrototypeOf(obj);
3 |
4 | let methods = methodNames;
5 | if (!methods) {
6 | const propNames = Object.getOwnPropertyNames(protoObj);
7 | methods = propNames.filter(name => typeof protoObj[name] === 'function');
8 | }
9 |
10 | methods.forEach(name => {
11 | obj[name] = protoObj[name].bind(obj);
12 | });
13 | }
14 |
--------------------------------------------------------------------------------
/src/bind-methods.test.js:
--------------------------------------------------------------------------------
1 | import bindMethods from './bind-methods';
2 |
3 | describe('bindMethods', function() {
4 |
5 | beforeEach(function() {
6 | class Test {
7 | constructor(foo) {
8 | this.foo = foo;
9 | }
10 | bar(arg) {
11 | return this.foo + arg;
12 | }
13 | baz(arg) {
14 | return this.foo + arg;
15 | }
16 | }
17 | this.Test = Test;
18 | });
19 |
20 | it('javascript 101', function() {
21 | const obj = new this.Test(100);
22 | expect(obj.bar(1)).to.equal(101);
23 | expect(obj.baz(2)).to.equal(102);
24 |
25 | const {bar, baz} = obj;
26 | const obj2 = {bar, baz, foo: 9000};
27 | expect(obj2.bar(1)).to.equal(9001);
28 | expect(obj2.baz(2)).to.equal(9002);
29 | });
30 |
31 | it('should bind all methods by default', function() {
32 | const obj = new this.Test(100);
33 |
34 | bindMethods(obj);
35 |
36 | const {bar, baz} = obj;
37 | const obj2 = {bar, baz, foo: 9000};
38 | expect(obj2.bar(1)).to.equal(101);
39 | expect(obj2.baz(2)).to.equal(102);
40 | });
41 |
42 | it('should bind only the specified methods', function() {
43 | const obj = new this.Test(100);
44 |
45 | bindMethods(obj, ['bar']);
46 |
47 | const {bar, baz} = obj;
48 | const obj2 = {bar, baz, foo: 9000};
49 | expect(obj2.bar(1)).to.equal(101);
50 | expect(obj2.baz(2)).to.equal(9002);
51 | });
52 |
53 | });
54 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | export {default} from './auth';
2 |
--------------------------------------------------------------------------------
/tools/.eslintrc.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | extends:
3 | - "../.eslintrc.yaml"
4 | - "../eslint/eslint-es2015.yaml"
5 |
6 |
--------------------------------------------------------------------------------
/tools/gruntfile.js:
--------------------------------------------------------------------------------
1 | import {grunt} from '../Gruntfile';
2 |
3 | const babel = {
4 | options: {
5 | sourceMap: 'inline',
6 | plugins: ['transform-runtime'],
7 | },
8 | build: {
9 | src: '**/*.js',
10 | expand: true,
11 | cwd: 'src',
12 | dest: 'build',
13 | },
14 | };
15 |
16 | const clean = {
17 | build: 'build',
18 | };
19 |
20 | const eslint = {
21 | src: {
22 | options: {configFile: 'src/.eslintrc.yaml'},
23 | src: ['src/**/*.js', '!<%= eslint.test.src %>'],
24 | },
25 | test: {
26 | options: {configFile: 'src/.eslintrc-test.yaml'},
27 | src: 'src/**/*.test.js',
28 | },
29 | examples: {
30 | options: {configFile: 'examples/.eslintrc.yaml'},
31 | src: 'examples/**/*.{js,jsx}',
32 | },
33 | tools: {
34 | options: {configFile: 'tools/.eslintrc.yaml'},
35 | src: 'tools/**/*.js',
36 | },
37 | gruntfile: {
38 | options: {configFile: '.eslintrc.yaml'},
39 | src: 'Gruntfile.js',
40 | },
41 | };
42 |
43 | const mochaTest = {
44 | test: {
45 | options: {
46 | reporter: 'spec',
47 | quiet: false,
48 | clearRequireCache: true,
49 | require: [
50 | 'babel-register',
51 | 'tools/test-globals',
52 | ],
53 | },
54 | src: '<%= eslint.test.src %>',
55 | },
56 | };
57 |
58 | const watch = {
59 | src: {
60 | files: ['<%= eslint.src.src %>'],
61 | tasks: ['eslint:src', 'mochaTest', 'build'],
62 | },
63 | test: {
64 | files: ['<%= eslint.test.src %>'],
65 | tasks: ['eslint:test', 'mochaTest'],
66 | },
67 | tools: {
68 | options: {reload: true},
69 | files: ['<%= eslint.tools.src %>'],
70 | tasks: ['eslint:tools'],
71 | },
72 | gruntfile: {
73 | options: {reload: true},
74 | files: ['<%= eslint.gruntfile.src %>'],
75 | tasks: ['eslint:gruntfile'],
76 | },
77 | lint: {
78 | options: {reload: true},
79 | files: ['.eslintrc*', 'eslint/*'],
80 | tasks: ['eslint'],
81 | },
82 | };
83 |
84 | grunt.initConfig({
85 | babel,
86 | clean,
87 | eslint,
88 | mochaTest,
89 | watch,
90 | });
91 |
92 | grunt.registerTask('build', ['clean', 'babel']);
93 | grunt.registerTask('test', ['eslint', 'mochaTest']);
94 | grunt.registerTask('default', ['watch']);
95 |
96 | grunt.loadNpmTasks('grunt-babel');
97 | grunt.loadNpmTasks('grunt-contrib-clean');
98 | grunt.loadNpmTasks('grunt-contrib-watch');
99 | grunt.loadNpmTasks('grunt-eslint');
100 | grunt.loadNpmTasks('grunt-mocha-test');
101 |
--------------------------------------------------------------------------------
/tools/test-globals.js:
--------------------------------------------------------------------------------
1 | import {assert, expect} from 'chai';
2 |
3 | global.assert = assert;
4 | global.expect = expect;
5 |
--------------------------------------------------------------------------------