Auth service receives login data

This commit is contained in:
Andrew Revinsky (DART)
2016-02-27 00:20:07 +03:00
parent f887c9fefe
commit 725814407d
27 changed files with 1036 additions and 252 deletions

View File

@@ -15,11 +15,14 @@
"autoprefixer-loader": "^2.0.0",
"babel-core": "6.1.4",
"babel-loader": "6.1.0",
"babel-plugin-add-module-exports": "^0.1.2",
"babel-plugin-transform-runtime": "6.1.4",
"babel-polyfill": "^6.1.4",
"babel-preset-es2015": "6.1.4",
"babel-preset-react": "6.1.4",
"babel-preset-stage-0": "6.1.2",
"babel-register": "6.1.4",
"babel-runtime": "^6.0.14",
"css-loader": "^0.14.4",
"del": "^1.2.0",
"extract-text-webpack-plugin": "^0.8.1",
@@ -45,13 +48,14 @@
"webpack-dev-server": "^1.9.0"
},
"dependencies": {
"babel-polyfill": "6.1.4",
"babel-runtime": "6.0.14",
"classnames": "^2.2.3",
"history": "1.17.0",
"immutable": "^3.7.6",
"invariant": "^2.1.1",
"isomorphic-fetch": "^2.2.1",
"js-cookie": "^2.1.0",
"object-pick": "^0.1.1",
"querystring": "^0.2.0",
"react": "^0.14.7",
"react-bootstrap": "^0.28.3",
"react-dom": "^0.14.0",
@@ -63,10 +67,9 @@
"react-router-redux": "^3.0.0",
"react-select": "^0.9.1",
"redux": "^3.0.2",
"redux-auth": "0.0.2",
"redux-batched-subscribe": "^0.1.4",
"redux-logger": "^2.5.2",
"redux-multi": "^0.1.9",
"redux-logger": "^2.6.0",
"redux-multi": "^0.1.91",
"redux-router": "^1.0.0-beta7",
"redux-thunk": "^1.0.3",
"uniloc": "^0.2.0"

View File

@@ -4,7 +4,8 @@
import React from "react";
import { createStore, compose, applyMiddleware, combineReducers} from "redux";
import { Provider} from "react-redux";
import { Provider, connect} from "react-redux";
import thunk from "redux-thunk";
import createLogger from 'redux-logger';
@@ -14,17 +15,16 @@ import { ReduxRouter} from "redux-router";
//import { Router, IndexRoute, Route, browserHistory } from 'react-router';
//import { syncHistory, routeReducer } from 'react-router-redux';
import { configure as reduxAuthConfigure, authStateReducer } from "redux-auth";
import { AuthGlobals } from "redux-auth/bootstrap-theme";
//import { configure as reduxAuthConfigure, authStateReducer } from "redux-auth";
//import { authStateReducer } from "redux-auth";
import authStateReducer from "./reducers/auth";
import { configure as reduxAuthConfigure } from './actions/configure';
//import { AuthGlobals } from "redux-auth/bootstrap-theme";
import { createHistory, createHashHistory, createMemoryHistory } from "history";
import { routerStateReducer, reduxReactRouter as clientRouter} from "redux-router";
import { pushState, routerStateReducer, reduxReactRouter as clientRouter} from "redux-router";
import { reduxReactRouter as serverRouter } from "redux-router/server";
import { connect } from 'react-redux';
import { pushState } from 'redux-router';
import { requireAuthentication } from './components/AuthComponent';
//import demoButtons from "./reducers/request-test-buttons";
@@ -35,10 +35,7 @@ import Account from "./views/Account";
import SignIn from "./views/SignIn";
import SignUp from "./views/SignUp";
//import GlobalComponents from "./views/partials/GlobalComponents";
// TODO: !!!!
// <GlobalComponents />
const AuthGlobals = () => (<div></div>);
class App extends React.Component {
render() {
@@ -60,26 +57,6 @@ export function initialize({cookies, isServer, currentLocation, userAgent} = {})
//demoUi
});
//let store;
//// access control method, used above in the "account" route
//const requireAuth = (nextState, transition, cb) => {
// // the setTimeout is necessary because of this bug:
// // https://github.com/rackt/redux-router/pull/62
// // this will result in a bunch of warnings, but it doesn't seem to be a serious problem
// setTimeout(() => {
// if (!store.getState().auth.getIn(["user", "isSignedIn"])) {
// transition(null, "/login");
// }
// cb();
// }, 0);
//};
// define app routes
// <Route path="account" component={Account} onEnter={requireAuth} />
//<Route path="account" component={requireAuthentication(Account)} />
const routes = (
<Route path="/" component={App}>
<IndexRoute component={requireAuthentication(MyAccounts)} />
@@ -95,11 +72,10 @@ export function initialize({cookies, isServer, currentLocation, userAgent} = {})
// create the redux store
const store = compose(
applyMiddleware(thunk),
applyMiddleware(createLogger()),
applyMiddleware(thunk, createLogger()),
reduxReactRouter({
createHistory: createHistoryMethod,
routes
routes,
createHistory: createHistoryMethod
})
)(createStore)(reducer);
@@ -107,44 +83,36 @@ export function initialize({cookies, isServer, currentLocation, userAgent} = {})
/**
* The React Router 1.0 routes for both the server and the client.
*/
return store.dispatch(((() => { debugger; })() , reduxAuthConfigure)([
return store.dispatch(reduxAuthConfigure([
{
default: {
//apiUrl: __API_URL__
apiUrl: '/',
emailSignInPath: 'login',
emailRegistrationPath: 'customers'
}
}
//, {
// evilUser: {
// //apiUrl: __API_URL__,
// apiUrl: '/api',
// signOutPath: "/mangs/sign_out",
// emailSignInPath: "/mangs/sign_in",
// emailRegistrationPath: "/mangs",
// accountUpdatePath: "/mangs",
// accountDeletePath: "/mangs",
// passwordResetPath: "/mangs/password",
// passwordUpdatePath: "/mangs/password",
// tokenValidationPath: "/mangs/validate_token",
// authProviderPaths: {
// github: "/mangs/github",
// facebook: "/mangs/facebook",
// google: "/mangs/google_oauth2"
// }
// }
//}
], {
cookies,
isServer,
currentLocation,
tokenFormat: {
"access-token": "{{ access-token }}",
"uid": "true"
"access-token": "{{ access-token }}"
},
initialCredentials: {
'uid': 123
handleLoginResponse: function(resp) {
debugger;
return resp.data;
},
handleAccountUpdateResponse: function(resp) {
debugger;
return resp.data;
},
handleTokenValidationResponse: function(resp) {
debugger;
return resp.data;
}
})).then(({ redirectPath, blank } = {}) => {
// hack for material-ui server-side rendering.

View File

@@ -1,7 +1,6 @@
/**
* Created by andrew on 25/02/16.
*/
import T from '../constants/ACTION_TYPES';
export function configureStart({...props} = {}) {
return {
@@ -24,12 +23,5 @@ export function configureError({errors, ...props} = {}) {
};
}
export function authenticateStart() {
return { type: T.AUTH.AUTHENTICATE_START };
}
export function authenticateComplete(user) {
return { type: T.AUTH.AUTHENTICATE_COMPLETE, user };
}
export function authenticateError(errors) {
return { type: T.AUTH.AUTHENTICATE_ERROR, errors };
}

View File

@@ -0,0 +1,14 @@
/**
* Created by andrew on 26/02/16.
*/
import T from '../constants/ACTION_TYPES';
export function authenticateStart() {
return { type: T.AUTH.AUTHENTICATE_START };
}
export function authenticateComplete(user) {
return { type: T.AUTH.AUTHENTICATE_COMPLETE, user };
}
export function authenticateError(errors) {
return { type: T.AUTH.AUTHENTICATE_ERROR, errors };
}

View File

@@ -0,0 +1,95 @@
/**
* Created by andrew on 26/02/16.
*/
import * as C from "../utils/constants";
import {
authenticateStart,
authenticateComplete,
authenticateError
} from "./authenticate";
import {applyConfig} from "../utils/clientSettings";
//import {
// showFirstTimeLoginSuccessModal,
// showFirstTimeLoginErrorModal,
// showPasswordResetSuccessModal,
// showPasswordResetErrorModal
//} from "./ui";
import {destroySession} from "../utils/sessionStorage";
import getRedirectInfo from "../utils/parseUrl";
import {pushState} from "redux-router";
export const SET_ENDPOINT_KEYS = "SET_ENDPOINT_KEYS";
export const STORE_CURRENT_ENDPOINT_KEY = "STORE_CURRENT_ENDPOINT_KEY";
export function setEndpointKeys(endpoints, currentEndpointKey, defaultEndpointKey) {
return { type: SET_ENDPOINT_KEYS, endpoints, currentEndpointKey, defaultEndpointKey };
};
export function storeCurrentEndpointKey(currentEndpointKey) {
return { type: STORE_CURRENT_ENDPOINT_KEY, currentEndpointKey };
};
export function configure(endpoint={}, settings={}) {
return dispatch => {
// don't render anything for OAuth redirects
if (settings.currentLocation && settings.currentLocation.match(/blank=true/)) {
return Promise.resolve({blank: true});
}
dispatch(authenticateStart());
let promise,
firstTimeLogin,
mustResetPassword,
user,
headers;
let {authRedirectPath, authRedirectHeaders} = getRedirectInfo(window.location);
if (authRedirectPath) {
dispatch(pushState(null, authRedirectPath));
}
if (authRedirectHeaders && authRedirectHeaders["access-token"]) {
debugger;
settings.initialCredentials = {
...settings.initialCredentials,
...authRedirectHeaders
};
}
// if tokens were invalidated by server, make sure to clear browser
// credentials
if (!settings.initialCredentials) {
destroySession();
}
promise = Promise.resolve(applyConfig({ dispatch, endpoint, settings }));
return promise
.then(user => {
dispatch(authenticateComplete(user));
return user;
})
.catch(({reason} = {}) => {
dispatch(authenticateError([reason]));
//if (firstTimeLogin) {
// dispatch(showFirstTimeLoginErrorModal());
//}
//
//if (mustResetPassword) {
// dispatch(showPasswordResetErrorModal());
//}
return Promise.resolve({reason});
});
};
}

View File

@@ -0,0 +1,70 @@
/**
* Created by andrew on 26/02/16.
*/
import {
getEmailSignInUrl,
setCurrentEndpointKey,
getCurrentEndpointKey
} from "../utils/sessionStorage";
import { storeCurrentEndpointKey } from "./configure";
import { parseResponse } from "../utils/handleFetchResponse";
import fetch from "../utils/fetch";
import T from '../constants/ACTION_TYPES';
import root from '../utils/root';
export function emailSignInFormUpdate(key, value) {
return { type: T.AUTH.SIGN_IN_FORM_UPDATE, key, value };
}
export function emailSignInStart() {
return { type: T.AUTH.SIGN_IN_START };
}
export function emailSignInComplete(user) {
return { type: T.AUTH.SIGN_IN_COMPLETE, user };
}
export function emailSignInError(errors) {
return { type: T.AUTH.SIGN_IN_ERROR, errors };
}
export function emailSignIn(body) {
return dispatch => {
// save previous endpoint key in case of failure
var prevEndpointKey = getCurrentEndpointKey();
const endpointKey = 'default';
// necessary for fetch to recognize the response as an api request
setCurrentEndpointKey(endpointKey);
var currentEndpointKey = getCurrentEndpointKey();
dispatch(storeCurrentEndpointKey(currentEndpointKey));
dispatch(emailSignInStart());
return fetch(getEmailSignInUrl(currentEndpointKey), {
headers: {
"Accept": "application/json",
"Content-Type": "application/json"
},
method: "post",
body: root.JSON.stringify(body)
})
.then(parseResponse)
.then(function(K) {
debugger;
return K;
})
.then((user) => dispatch(emailSignInComplete(user)))
.catch((errors) => {
// revert endpoint key to what it was before failed request
setCurrentEndpointKey(prevEndpointKey);
dispatch(storeCurrentEndpointKey(prevEndpointKey));
return dispatch(emailSignInError(errors));
});
};
}

View File

@@ -10,7 +10,6 @@ export function requireAuthentication(Component) {
class AuthComponent extends React.Component {
componentWillMount() {
debugger;
if (!this.props.isAuthenticated) {
// redirect to login and add next param so we can redirect again after login
@@ -20,6 +19,11 @@ export function requireAuthentication(Component) {
}
render() {
if (!this.props.isAuthenticated) {
return (<div className="panel">
<h2 className="text-danger">No anonymous access!</h2>
</div>)
}
// render the component that requires auth (passed to this wrapper)
return (
<Component {...this.props} />
@@ -29,12 +33,12 @@ export function requireAuthentication(Component) {
const mapStateToProps =
(state) => {
debugger;
console.info('state', state);
return ({
token: state.auth.token,
//token: state.auth.token,
//userName: state.auth.userName,
//isAuthenticated: state.auth.isAuthenticated
isAuthenticated: state.auth.getIn(['user', 'isSignedIn'])
isAuthenticated: state.auth.user.isSignedIn
})
};

View File

@@ -4,7 +4,10 @@
import React, { PropTypes } from "react";
import { Grid, Col, Navbar, NavItem, Nav, NavbarBrand, Footer } from "react-bootstrap";
import { LinkContainer } from "react-router-bootstrap";
import { SignOutButton } from "redux-auth/bootstrap-theme";
//import { SignOutButton } from "redux-auth/bootstrap-theme";
const SignOutButton = () => (<div>SignOutButton!</div>);
//if (!global.__SERVER__ && !global.__TEST__) {
// require("../../styles/main.scss");
@@ -30,7 +33,7 @@ class Container extends React.Component {
<NavItem eventKey={2}>Account</NavItem>
</LinkContainer>
</Nav>
<Nav right={true}>
<Nav pullRight={true}>
<SignOutButton></SignOutButton>
</Nav>
</Navbar>

View File

@@ -2,15 +2,17 @@
* Created by andrew on 15/02/16.
*/
import React, {PropTypes} from "react";
import auth from "redux-auth";
//import auth from "redux-auth";
import * as BS from "react-bootstrap";
import Input from "./Input";
import ButtonLoader from "./ButtonLoader";
import { emailSignInFormUpdate, emailSignIn } from "redux-auth";
//import { emailSignInFormUpdate, emailSignIn } from "redux-auth";
import { Glyphicon } from "react-bootstrap";
import { connect } from "react-redux";
import { emailSignInFormUpdate, emailSignIn } from "../../actions/signIn";
/*
<Input type="password"
label="Password"
@@ -23,7 +25,21 @@ import { connect } from "react-redux";
{...this.props.inputProps.password} />
*/
function read(src, path = '', defaultVal = '') {
const [pathItem = null, ...rest] = path.split('.');
if (pathItem === null ) {
return src;
} else if (rest.length === 0) {
if (!src) { return defaultVal; }
return src[pathItem];
} else {
if (!src) { return defaultVal; }
return read(src[pathItem], rest.join('.'));
}
}
class EmailSignInForm extends React.Component {
static propTypes = {
endpoint: PropTypes.string,
inputProps: PropTypes.shape({
@@ -41,28 +57,22 @@ class EmailSignInForm extends React.Component {
}
};
getEndpoint () {
return (
this.props.endpoint ||
this.props.auth.getIn(["configure", "currentEndpointKey"]) ||
this.props.auth.getIn(["configure", "defaultEndpointKey"])
);
}
handleInput (key, val) {
this.props.dispatch(emailSignInFormUpdate(this.getEndpoint(), key, val));
this.props.dispatch(emailSignInFormUpdate(key, val));
}
handleSubmit (event) {
event.preventDefault();
let formData = this.props.auth.getIn(["emailSignIn", this.getEndpoint(), "form"]).toJS();
this.props.dispatch(emailSignIn(formData, this.getEndpoint()));
let formData = { ...this.props.auth.signIn.form };
this.props.dispatch(emailSignIn(formData, null));
}
render () {
try {
let disabled = (
this.props.auth.getIn(["user", "isSignedIn"]) ||
this.props.auth.getIn(["emailSignIn", this.getEndpoint(), "loading"])
this.props.auth.user.isSignedIn ||
this.props.auth.signIn.loading
);
return (
@@ -74,12 +84,12 @@ class EmailSignInForm extends React.Component {
placeholder="Email"
name="email"
disabled={disabled}
value={this.props.auth.getIn(["emailSignIn", this.getEndpoint(), "form", "email"])}
errors={this.props.auth.getIn(["emailSignIn", this.getEndpoint(), "errors", "email"])}
value={read(this.props.auth, 'signIn.form.email', '')}
errors={read(this.props.auth, 'signIn.errors.email', null)}
onChange={this.handleInput.bind(this, "email")}
{...this.props.inputProps.email} />
<ButtonLoader loading={this.props.auth.getIn(["emailSignIn", this.getEndpoint(), "loading"])}
<ButtonLoader loading={read(this.props.auth, 'signIn.loading', false)}
type="submit"
icon={<Glyphicon glyph="log-in" />}
className='email-sign-in-submit pull-right'
@@ -90,6 +100,10 @@ class EmailSignInForm extends React.Component {
</ButtonLoader>
</form>
);
} catch (ex){
console.error('Render exception: ', ex);
return [' ERROR '];
}
}
}

View File

@@ -2,16 +2,20 @@
* Created by andrew on 15/02/16.
*/
import React, {PropTypes} from "react";
import auth from "redux-auth";
//import auth from "redux-auth";
import Input from "./Input";
import ButtonLoader from "./ButtonLoader";
import { emailSignUpFormUpdate, emailSignUp } from "redux-auth";
//import { emailSignUpFormUpdate, emailSignUp } from "redux-auth";
import IndexPanel from "./../../components/partials/IndexPanel";
import { customerInfoMap } from '../../entities/formToPayloadMappers';
import { Glyphicon } from "react-bootstrap";
import { connect } from "react-redux";
const emailSignUpFormUpdate = () => {debugger; },
emailSignUp = () => {debugger; };
class EmailSignUpForm extends React.Component {
static propTypes = {
endpoint: PropTypes.string,

View File

@@ -1,3 +1,23 @@
/**
* Created by andrew on 25/02/16.
*/
import T from '../../constants/ACTION_TYPES';
const authInitialState = {
loading: false,
valid: false,
errors: null
};
export const authReducer = (state = {...authInitialState}, action) => {
switch(action.type) {
case T.AUTH.AUTHENTICATE_START: return {...state, loading: true };
case T.AUTH.AUTHENTICATE_COMPLETE: return {...state, loading: false,
errors: null,
valid: true};
case T.AUTH.AUTHENTICATE_ERROR: return {...state, loading: false,
errors: "Invalid token",
valid: false};
default: return state;
}
};

View File

@@ -1,3 +1,37 @@
/**
* Created by andrew on 25/02/16.
*/
import T from '../../constants/ACTION_TYPES';
const configInitialState = {
loading: true,
errors: null,
config: null
};
export const configReducer = (state = {...configInitialState}, action) => {
switch(action.type) {
case T.AUTH.CONFIGURE_START:
return {
...state,
loading: true
};
case T.AUTH.CONFIGURE_COMPLETE:
const { config } = action;
return {
...state,
loading: false,
errors: null,
config
};
case T.AUTH.CONFIGURE_ERROR:
const { errors } = action;
return {
...state,
loading: false,
errors
};
default:
return state;
}
};

View File

@@ -1,143 +1,20 @@
/**
* Created by andrew on 25/02/16.
*/
import T from '../../constants/ACTION_TYPES';
import { combineReducers } from 'redux';
const configInitialState = {
loading: true,
errors: null,
config: null
};
import { configReducer } from './configure';
import { authReducer } from './authenticate';
import { signInReducer } from './signin';
import { signOutReducer } from './signout';
import { userReducer } from './user';
export const configReducer = (state = {...configInitialState}, action) => {
switch(action.type) {
case T.AUTH.CONFIGURE_START:
return {
...state,
loading: true
};
case T.AUTH.CONFIGURE_COMPLETE:
const { config } = action;
return {
...state,
loading: false,
errors: null,
config
};
case T.AUTH.CONFIGURE_ERROR:
const { errors } = action;
return {
...state,
loading: false,
errors
};
default:
return state;
}
};
const authStateReducer = combineReducers({
configure: configReducer,
signIn: signInReducer,
signOut: signOutReducer,
authentication: authReducer,
user: userReducer
});
const authInitialState = {
loading: false,
valid: false,
errors: null
};
export const authReducer = (state = {...authInitialState}, action) => {
switch(action.type) {
case T.AUTH.AUTHENTICATE_START: return {...state, loading: true };
case T.AUTH.AUTHENTICATE_COMPLETE: return {...state, loading: false,
errors: null,
valid: true};
case T.AUTH.AUTHENTICATE_ERROR: return {...state, loading: false,
errors: "Invalid token",
valid: false};
default: return state;
}
};
const signInInitialState = {
loading: false,
errors: null,
form: {}
};
export const signInReducer = (state = { ...signInInitialState, form: {...signInInitialState.form }}, action) => {
switch(action.type) {
case T.AUTH.SIGN_IN_START:
return {
...state,
loading: true
};
case T.AUTH.SIGN_IN_COMPLETE:
return {
...signInInitialState,
form: { ...signInInitialState.form }
};
case T.AUTH.SIGN_IN_ERROR:
const { errors } = action;
return {
...state,
loading: false,
errors
};
case T.AUTH.SIGN_IN_FORM_UPDATE:
const { key, value } = action;
return {
...state,
form: {
...state.form,
[key]: value
}
};
default: return state;
}
};
const signOutInitialState = {
loading: false,
errors: null
};
export const signOutReducer = (state = {...signOutInitialState}, action) => {
switch(action.type) {
case T.AUTH.SIGN_OUT_START:
return {
...state,
loading: true
};
case T.AUTH.SIGN_OUT_COMPLETE:
return {
...state,
loading: false,
errors: null
};
default: return state;
}
};
const userInitalState = {
attributes: null,
isSignedIn: false
};
export const userReducer = (state = {...userInitalState}, action) => {
switch(action.type) {
case T.AUTH.AUTHENTICATE_COMPLETE:
const { user } = action;
return {...state,
attributes: user,
isSignedIn: true
};
case T.AUTH.SIGN_IN_COMPLETE:
const { user } = action;
return {...state,
attributes: user.data,
isSignedIn: true
};
case T.AUTH.SIGN_OUT_COMPLETE:
case T.AUTH.AUTHENTICATE_ERROR:
return {
...userInitalState
};
default: return state;
}
};
export default authStateReducer;

View File

@@ -1,3 +1,42 @@
/**
* Created by andrew on 25/02/16.
*/
import T from '../../constants/ACTION_TYPES';
const signInInitialState = {
loading: false,
errors: null,
form: {}
};
export const signInReducer = (state = { ...signInInitialState, form: {...signInInitialState.form }}, action) => {
switch(action.type) {
case T.AUTH.SIGN_IN_START:
return {
...state,
loading: true
};
case T.AUTH.SIGN_IN_COMPLETE:
return {
...signInInitialState,
form: { ...signInInitialState.form }
};
case T.AUTH.SIGN_IN_ERROR:
const { errors } = action;
return {
...state,
loading: false,
errors
};
case T.AUTH.SIGN_IN_FORM_UPDATE:
const { key, value } = action;
return {
...state,
form: {
...state.form,
[key]: value
}
};
default: return state;
}
};

View File

@@ -1,3 +1,26 @@
/**
* Created by andrew on 25/02/16.
*/
import T from '../../constants/ACTION_TYPES';
const signOutInitialState = {
loading: false,
errors: null
};
export const signOutReducer = (state = {...signOutInitialState}, action) => {
switch(action.type) {
case T.AUTH.SIGN_OUT_START:
return {
...state,
loading: true
};
case T.AUTH.SIGN_OUT_COMPLETE:
return {
...state,
loading: false,
errors: null
};
default: return state;
}
};

View File

@@ -1,3 +1,36 @@
/**
* Created by andrew on 25/02/16.
*/
import T from '../../constants/ACTION_TYPES';
const userInitalState = {
attributes: null,
isSignedIn: false
};
export const userReducer = (state = {...userInitalState}, action) => {
switch(action.type) {
case T.AUTH.AUTHENTICATE_COMPLETE: {
const { user } = action;
return {...state,
attributes: user,
isSignedIn: true
};
}
case T.AUTH.SIGN_IN_COMPLETE: {
const { user } = action;
return {...state,
attributes: user.data,
isSignedIn: true
};
}
case T.AUTH.SIGN_OUT_COMPLETE:
case T.AUTH.AUTHENTICATE_ERROR:
return {
...userInitalState
};
default: return state;
}
};

View File

@@ -0,0 +1,117 @@
/**
* Created by andrew on 26/02/16.
*/
import * as C from "./constants";
import fetch from "./fetch";
import parseEndpointConfig from "./parseEndpointConfig";
import { setEndpointKeys } from "../actions/configure";
import {
getCurrentSettings,
setCurrentSettings,
getInitialEndpointKey,
setDefaultEndpointKey,
setCurrentEndpoint,
setCurrentEndpointKey,
retrieveData,
persistData
} from "./sessionStorage";
// can't use "window" with node app
var root = Function("return this")() || (42, eval)("this");
const defaultSettings = {
//proxyIf: function() { return false; },
//proxyUrl: "/proxy",
forceHardRedirect: false,
storage: "cookies",
cookieExpiry: 14,
cookiePath: "/",
initialCredentials: null,
passwordResetSuccessUrl: function() {
return root.location.href;
},
confirmationSuccessUrl: function() {
return root.location.href;
},
tokenFormat: {
"access-token": "{{ access-token }}"
//"token-type": "Bearer",
//client: "{{ client }}",
//expiry: "{{ expiry }}",
//uid: "{{ uid }}"
},
parseExpiry: function(headers){
// convert from ruby time (seconds) to js time (millis)
return (parseInt(headers["expiry"], 10) * 1000) || null;
},
handleLoginResponse: function(resp) {
return resp.data;
},
handleAccountUpdateResponse: function(resp) {
return resp.data;
},
handleTokenValidationResponse: function(resp) {
return resp.data;
}
};
// save session configuration
export function applyConfig({ dispatch, endpoint={}, settings={}, reset=false } = {}) {
let currentEndpointKey;
if (reset) {
resetConfig();
}
if (settings.initialCredentials) {
currentEndpointKey = settings.initialCredentials.currentEndpointKey;
}
setCurrentSettings({ ...defaultSettings, ...settings });
let { defaultEndpointKey, currentEndpoint } = parseEndpointConfig(
endpoint, getInitialEndpointKey()
);
if (!currentEndpointKey) {
currentEndpointKey = defaultEndpointKey;
}
// persist default config key with session storage
setDefaultEndpointKey(defaultEndpointKey);
setCurrentEndpoint(currentEndpoint);
dispatch(setEndpointKeys(Object.keys(currentEndpoint), currentEndpointKey, defaultEndpointKey));
setCurrentEndpointKey(currentEndpointKey);
if (getCurrentSettings().initialCredentials) {
// skip initial headers check (i.e. check was already done server-side)
let { user, headers, config } = getCurrentSettings().initialCredentials;
persistData(C.SAVED_CREDS_KEY, headers);
return Promise.resolve(user);
}
const savedCreds = retrieveData(C.SAVED_CREDS_KEY);
if (savedCreds) {
// verify session credentials with API
debugger;
return fetch(savedCreds)
}
return Promise.reject({ reason: "No credentials." })
}

View File

@@ -0,0 +1,7 @@
/**
* Created by andrew on 26/02/16.
*/
export const INITIAL_CONFIG_KEY = "default";
export const DEFAULT_CONFIG_KEY = "defaultConfigKey";
export const SAVED_CONFIG_KEY = "currentConfigName";
export const SAVED_CREDS_KEY = "authHeaders";

View File

@@ -0,0 +1,79 @@
/**
* Created by andrew on 26/02/16.
*/
import originalFetch from "isomorphic-fetch";
import * as C from "./constants";
//import extend from "extend";
import {
getApiUrl,
retrieveData,
persistData,
getTokenFormat,
getSessionEndpointKey,
isApiRequest
} from "./sessionStorage";
function getAuthHeaders(url) {
if (isApiRequest(url)) {
// fetch current auth headers from storage
const currentHeaders = retrieveData(C.SAVED_CREDS_KEY) || {},
nextHeaders = {};
// bust IE cache
nextHeaders["If-Modified-Since"] = "Mon, 26 Jul 1997 05:00:00 GMT";
// set header for each key in `tokenFormat` config
for (var key in getTokenFormat()) {
nextHeaders[key] = currentHeaders[key];
}
return nextHeaders;
} else {
return {};
}
}
function updateAuthCredentials(resp) {
// check config apiUrl matches the current response url
if (isApiRequest(resp.url)) {
// set header for each key in `tokenFormat` config
var newHeaders = {};
// set flag to ensure that we don't accidentally nuke the headers
// if the response tokens aren't sent back from the API
var blankHeaders = true;
// set header key + val for each key in `tokenFormat` config
for (var key in getTokenFormat()) {
newHeaders[key] = resp.headers.get(key);
if (newHeaders[key]) {
blankHeaders = false;
}
}
// persist headers for next request
if (!blankHeaders) {
persistData(C.SAVED_CREDS_KEY, newHeaders);
}
}
return resp;
}
export default function (url, options = {}) {
if (!options.headers) {
options.headers = {}
}
options.headers = {
...options.headers,
...getAuthHeaders(url)
};
//extend(options.headers, getAuthHeaders(url));
return originalFetch(url, options)
.then(resp => updateAuthCredentials(resp));
}

View File

@@ -0,0 +1,11 @@
/**
* Created by andrew on 26/02/16.
*/
export function parseResponse (response) {
let json = response.json();
if (response.status >= 200 && response.status < 300) {
return json;
} else {
return json.then(err => Promise.reject(err));
}
}

View File

@@ -0,0 +1,65 @@
/**
* Created by andrew on 26/02/16.
*/
import * as C from "./constants";
// base endpoint that other endpoints extend from
const defaultEndpoint = {
apiUrl: "/api",
signOutPath: "/auth/sign_out",
emailSignInPath: "/auth/sign_in",
emailRegistrationPath: "/auth",
accountUpdatePath: "/auth",
accountDeletePath: "/auth",
passwordResetPath: "/auth/password",
passwordUpdatePath: "/auth/password",
tokenValidationPath: "/auth/validate_token",
authProviderPaths: {
github: "/auth/github",
facebook: "/auth/facebook",
google: "/auth/google_oauth2"
}
};
function getFirstObjectKey (obj) {
for (var key in obj) {
return key;
}
}
export default function parseEndpointConfig(endpoint, defaultEndpointKey = null) {
// normalize so opts is always an array of objects
if (endpoint.constructor !== Array) {
// single config will always be called 'default' unless set
// by previous session
defaultEndpointKey = C.INITIAL_CONFIG_KEY;
// config should look like {default: {...}}
var defaultConfig = {};
defaultConfig[defaultEndpointKey] = endpoint;
// endpoint should look like [{default: {...}}]
endpoint = [defaultConfig];
}
let currentEndpoint = {};
// iterate over config items, extend each from defaults
for (var i = 0; i < endpoint.length; i++) {
var configName = getFirstObjectKey(endpoint[i]);
// set first as default config
if (!defaultEndpointKey) {
defaultEndpointKey = configName;
}
// save config to `configs` hash
currentEndpoint[configName] = {
...defaultEndpoint,
...endpoint[i][configName]
};
}
return { defaultEndpointKey, currentEndpoint };
}

View File

@@ -0,0 +1,134 @@
/**
* Created by andrew on 26/02/16.
*/
import querystring from "querystring";
export function normalizeTokenKeys (params) {
// normalize keys
if (params.token) {
params["access-token"] = params.token;
delete params.token;
}
if (params.auth_token) {
params["access-token"] = params.auth_token;
delete params.auth_token;
}
if (params.client_id) {
params.client = params.client_id;
delete params.client_id;
}
if (params.config) {
params.endpointKey = params.config;
delete params.config;
}
return params;
};
const getAnchorSearch = function(location) {
const rawAnchor = location.anchor || "",
arr = rawAnchor.split("?");
return (arr.length > 1) ? arr[1] : null;
};
const getSearchQs = function(location) {
const rawQs = location.search || "",
qs = rawQs.replace("?", ""),
qsObj = (qs) ? querystring.parse(qs) : {};
return qsObj;
};
const getAnchorQs = function(location) {
const anchorQs = getAnchorSearch(location),
anchorQsObj = (anchorQs) ? querystring.parse(anchorQs) : {};
return anchorQsObj;
};
const stripKeys = function(obj, keys) {
for (var q in keys) {
delete obj[keys[q]];
}
return obj;
};
export function getAllParams (location) {
return {
...getAnchorQs(location),
...getSearchQs(location)
};
};
const buildCredentials = function(location, keys) {
const params = getAllParams(location);
let authHeaders = {};
for (var key of keys) {
authHeaders[key] = params[key];
}
return normalizeTokenKeys(authHeaders);
};
// this method is tricky. we want to reconstruct the current URL with the
// following conditions:
// 1. search contains none of the supplied keys
// 2. anchor search (i.e. `#/?key=val`) contains none of the supplied keys
// 3. all of the keys NOT supplied are presevered in their original form
// 4. url protocol, host, and path are preserved
const getLocationWithoutParams = function(currentLocation, keys) {
// strip all values from both actual and anchor search params
let newSearch = querystring.stringify(stripKeys(getSearchQs(currentLocation), keys)),
newAnchorQs = querystring.stringify(stripKeys(getAnchorQs(currentLocation), keys)),
newAnchor = (currentLocation.hash || "").split("?")[0];
if (newSearch) {
newSearch = "?" + newSearch;
}
if (newAnchorQs) {
newAnchor += "?" + newAnchorQs;
}
if (newAnchor && !newAnchor.match(/^#/)) {
newAnchor = "#/" + newAnchor;
}
// reconstruct location with stripped auth keys
const newLocation = currentLocation.pathname + newSearch + newAnchor;
return newLocation;
};
export default function getRedirectInfo(currentLocation) {
if (!currentLocation) {
return {};
} else {
let authKeys = [
"access-token",
"token",
"auth_token",
"config",
"client",
"client_id",
"expiry",
"uid",
"reset_password",
"account_confirmation_success"
];
var authRedirectHeaders = buildCredentials(currentLocation, authKeys);
var authRedirectPath = getLocationWithoutParams(currentLocation, authKeys);
if (authRedirectPath !== currentLocation) {
return {authRedirectHeaders, authRedirectPath};
} else {
return {};
}
}
}

View File

@@ -0,0 +1,4 @@
/**
* Created by andrew on 27/02/16.
*/
export default Function("return this")() || (42, eval)("this");

View File

@@ -0,0 +1,176 @@
/**
* Created by andrew on 26/02/16.
*/
import Cookies from "js-cookie";
import * as C from "./constants";
//import "babel-polyfill";
// even though this code shouldn't be used server-side, node will throw
// errors if "window" is used
var root = Function("return this")() || (42, eval)("this");
// stateful variables that persist throughout session
root.authState = {
currentSettings: {},
currentEndpoint: {},
defaultEndpointKey: null
};
export function setCurrentSettings (s) {
root.authState.currentSettings = s;
}
export function getCurrentSettings () {
return root.authState.currentSettings;
}
export function setCurrentEndpoint (e) {
root.authState.currentEndpoint = e;
}
export function getCurrentEndpoint () {
return root.authState.currentEndpoint;
}
export function setCurrentEndpointKey (k) {
persistData(C.SAVED_CONFIG_KEY, k || getDefaultEndpointKey());
}
export function getCurrentEndpointKey () {
return retrieveData(C.SAVED_CONFIG_KEY) || getDefaultEndpointKey();
}
export function setDefaultEndpointKey (k) {
persistData(C.DEFAULT_CONFIG_KEY, k);
}
export function getDefaultEndpointKey () {
return retrieveData(C.DEFAULT_CONFIG_KEY);
}
// reset stateful variables
export function resetConfig () {
root.authState = root.authState || {};
root.authState.currentSettings = {};
root.authState.currentEndpoint = {};
destroySession();
}
export function destroySession () {
var sessionKeys = [
C.SAVED_CREDS_KEY,
C.SAVED_CONFIG_KEY
];
for (var key in sessionKeys) {
key = sessionKeys[key];
// kill all local storage keys
if (root.localStorage) {
root.localStorage.removeItem(key);
}
// remove from base path in case config is not specified
Cookies.remove(key, {
path: root.authState.currentSettings.cookiePath || "/"
});
}
}
function unescapeQuotes (val) {
return val && val.replace(/("|')/g, "");
};
export function getInitialEndpointKey () {
return unescapeQuotes(
Cookies.get(C.SAVED_CONFIG_KEY) ||
(root.localStorage && root.localStorage.getItem(C.SAVED_CONFIG_KEY))
);
}
export function isApiRequest(url) {
return true;
}
// TODO: make this really work
export function getSessionEndpointKey (k) {
let key = k || getCurrentEndpointKey();
if (!key) {
throw "You must configure redux-auth before use.";
} else {
return key;
}
}
export function getSessionEndpoint (k) {
return getCurrentEndpoint()[getSessionEndpointKey(k)];
}
// only should work for current session
export function getSignOutUrl (endpointKey) {
return `${getApiUrl(endpointKey)}${getSessionEndpoint(endpointKey).signOutPath}`
}
export function getEmailSignInUrl (endpointKey) {
return `${getApiUrl(endpointKey)}${getSessionEndpoint(endpointKey).emailSignInPath}`
}
export function getEmailSignUpUrl (endpointKey) {
return `${getApiUrl(endpointKey)}${getSessionEndpoint(endpointKey).emailRegistrationPath}?config_name=${endpointKey}`
}
export function getApiUrl(key) {
let configKey = getSessionEndpointKey(key);
return root.authState.currentEndpoint[configKey].apiUrl;
}
export function getTokenFormat() {
return root.authState.currentSettings.tokenFormat;
}
export function persistData (key, val) {
val = root.JSON.stringify(val);
switch (root.authState.currentSettings.storage) {
case "localStorage":
root.localStorage.setItem(key, val);
break;
default:
Cookies.set(key, val, {
expires: root.authState.currentSettings.cookieExpiry,
path: root.authState.currentSettings.cookiePath
});
break;
}
}
export function retrieveData (key) {
var val = null;
switch (root.authState.currentSettings.storage) {
case "localStorage":
val = root.localStorage && root.localStorage.getItem(key);
break;
default:
val = Cookies.get(key);
break;
}
// if value is a simple string, the parser will fail. in that case, simply
// unescape the quotes and return the string.
try {
// return parsed json response
return JSON.parse(val);
} catch (err) {
// unescape quotes
return unescapeQuotes(val);
}
};

View File

@@ -5,7 +5,6 @@ import React from "react";
import { PageHeader, OverlayTrigger, Tooltip, Grid, Col, Row, Nav, NavItem, ButtonGroup, Button, Table } from "react-bootstrap";
import { Link, IndexLink} from "react-router";
import { connect } from "react-redux";
import * as BSTheme from "redux-auth/bootstrap-theme";
//import * as DefaultTheme from "redux-auth";
import Select from "react-select";
import * as Modals from './modals';
@@ -218,10 +217,10 @@ class MyAccounts extends React.Component {
export default connect(({auth, demoUi = new Map()}) => {
return ({
currentUserUid: auth.getIn(["user", "attributes", "provider"]) || "none",
currentUserProvider: auth.getIn(["user", "attributes", "uid"]) || "none",
currentUserEndpoint: auth.getIn(["user", "endpointKey"]) || "none",
currentUserUid: auth.user && auth.user.attributes && auth.user.attributes.provider || "none",
currentUserProvider: auth.user && auth.user.attributes && auth.user.attributes.uid || "none",
currentUserEndpoint: "none",
//theme: demoUi.get("theme"),
pageEndpoint: demoUi.get("endpoint")
pageEndpoint: null
})
})(MyAccounts);

View File

@@ -21,10 +21,8 @@ import EmailSignInForm from "../controls/bootstrap/EmailSignInForm";
export class SignIn extends React.Component {
componentWillMount() {
debugger;
if (this.props.isAuthenticated) {
debugger;
//// redirect to login and add next param so we can redirect again after login
//const redirectAfterLogin = this.props.location.pathname;

View File

@@ -30,13 +30,14 @@ export default (DEBUG, PATH, PORT=3000) => ({
// Load ES6/JSX
{ test: /\.jsx?$/,
include: [
path.resolve(__dirname, "src"),
path.resolve(__dirname, "node_modules/redux-auth/src/views/bootstrap")
path.resolve(__dirname, "src")
//,
//path.resolve(__dirname, "node_modules/redux-auth/src/views/bootstrap")
],
loader: "babel-loader",
query: {
plugins: ['transform-runtime'],
presets: ['es2015', 'stage-0', 'react'],
presets: ['es2015', 'react', 'stage-0']
}
},
@@ -78,11 +79,11 @@ export default (DEBUG, PATH, PORT=3000) => ({
mangle: {screw_ie8: true, keep_fnames: true}
}),
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.optimize.AggressiveMergingPlugin(),
new webpack.optimize.AggressiveMergingPlugin()
],
resolveLoader: {
root: path.join(__dirname, "node_modules"),
root: path.join(__dirname, "node_modules")
},
resolve: {
@@ -97,6 +98,6 @@ export default (DEBUG, PATH, PORT=3000) => ({
},
// Allow to omit extensions when requiring these files
extensions: ["", ".js", ".jsx"],
extensions: ["", ".js", ".jsx"]
}
});