Authentication & user data retrieval fixed, API methods provided

This commit is contained in:
Andrew Revinsky (DART)
2016-03-15 04:17:55 +03:00
parent f4ecc093fe
commit c11f1b1a64
19 changed files with 291 additions and 154 deletions

View File

@@ -89,13 +89,21 @@ gulp.task('serve:start', ['serve:static'], () => {
//}
proxy: {
'/user' : {
target: 'http://localhost:8080'
},
'/login' : {
target: 'http://localhost:8080'
},
'/customers' : {
target: 'http://localhost:8080'
},
'/accounts' : {
target: 'http://localhost:8080'
},
'/transfers' : {
target: 'http://localhost:8080'
}
}
})
.listen(PORT, '0.0.0.0', (err) => {

View File

@@ -88,7 +88,9 @@ export function initialize({cookies, isServer, currentLocation, userAgent} = {})
default: {
//apiUrl: '/',
emailSignInPath: '/login',
emailRegistrationPath: '/customers'
emailRegistrationPath: '/customers',
currentUserPath: '/user',
accountsPath: '/accounts'
}
}
], {

View File

@@ -3,6 +3,27 @@
*/
import T from '../constants/ACTION_TYPES';
import {
getCurrentSettings,
setCurrentSettings,
getInitialEndpointKey,
setDefaultEndpointKey,
setCurrentEndpoint,
setCurrentEndpointKey,
retrieveData,
persistData,
destroySession,
persistUserData,
retrieveUserData,
retrieveHeaders
} from "../utils/sessionStorage";
import {
apiGetCurrentUser
} from '../utils/api';
import {entityReceived } from './entities';
export function authenticateStart() {
return { type: T.AUTH.AUTHENTICATE_START };
}
@@ -12,3 +33,54 @@ export function authenticateComplete(user) {
export function authenticateError(errors) {
return { type: T.AUTH.AUTHENTICATE_ERROR, errors };
}
export function authenticate() {
return dispatch => {
dispatch(authenticateStart());
const savedUserPromise = new Promise((rs, rj) => {
const currentHeaders = retrieveHeaders();
const accessToken = currentHeaders["access-token"];
if (!accessToken) {
return rj({ reason: 'no token'});
}
const savedUser = retrieveUserData();
if (savedUser) {
return rs(savedUser);
}
return apiGetCurrentUser().then((userData) => {
persistUserData(userData);
dispatch(entityReceived(userData.id, userData));
rs(userData);
}, (err) => {
debugger;
rj(err);
});
});
return savedUserPromise
.then(user => {
dispatch(authenticateComplete(user));
return user;
})
.catch(({reason} = {}) => {
dispatch(authenticateError([reason]));
return Promise.resolve({reason});
});
};
}

View File

@@ -3,6 +3,7 @@
*/
import * as C from "../utils/constants";
import {
authenticate,
authenticateStart,
authenticateComplete,
authenticateError
@@ -22,9 +23,8 @@ import {applyConfig} from "../utils/clientSettings";
// showPasswordResetErrorModal
//} from "./ui";
import {destroySession} from "../utils/sessionStorage";
import getRedirectInfo from "../utils/parseUrl";
import {pushState} from "redux-router";
import { pushState } from "redux-router";
import root from '../utils/root';
export const SET_ENDPOINT_KEYS = "SET_ENDPOINT_KEYS";
@@ -42,68 +42,10 @@ 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(root.location);
// TODO: FiX!
//if (authRedirectPath) {
// dispatch(pushState(null, authRedirectPath));
//}
const currentHeaders = retrieveData(C.SAVED_CREDS_KEY) || {};
//if (authRedirectHeaders && authRedirectHeaders["access-token"]) {
if (currentHeaders && currentHeaders["access-token"]) {
//settings.initialCredentials = {
// ...(settings.initialCredentials || {}),
// ...authRedirectHeaders,
// ...currentHeaders
//};
} else {
destroySession();
}
// 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});
return applyConfig({ dispatch, endpoint, settings })
.then(() => {
return dispatch(authenticate());
});
};

View File

@@ -3,7 +3,8 @@
*/
import {
setCurrentEndpointKey,
getCurrentEndpointKey
getCurrentEndpointKey,
persistUserData
} from "../utils/sessionStorage";
import { entityReceived } from './entities';
@@ -36,39 +37,34 @@ export function emailSignInError(errors) {
export function emailSignIn(body) {
return dispatch => {
// save previous endpoint key in case of failure
var prevEndpointKey = getCurrentEndpointKey();
//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));
//setCurrentEndpointKey(endpointKey);
//var currentEndpointKey = getCurrentEndpointKey();
//
//dispatch(storeCurrentEndpointKey(currentEndpointKey));
dispatch(emailSignInStart());
return apiSignIn(body)
.then(function(data = {}) {
const { id, customerInfo } = data;
if (id && customerInfo) {
const user = {
...customerInfo,
uid: id
};
debugger;
dispatch(entityReceived(id, user));
return user;
const { id } = data;
if (id ) {
dispatch(entityReceived(id, data));
}
return data;
})
.then((user) => {
persistUserData(user);
dispatch(emailSignInComplete(user));
})
.catch((errors) => {
// revert endpoint key to what it was before failed request
setCurrentEndpointKey(prevEndpointKey);
dispatch(storeCurrentEndpointKey(prevEndpointKey));
//setCurrentEndpointKey(prevEndpointKey);
//dispatch(storeCurrentEndpointKey(prevEndpointKey));
return dispatch(emailSignInError(errors));
});
};

View File

@@ -9,25 +9,37 @@ export function requireAuthentication(Component) {
class AuthComponent extends React.Component {
componentWillMount() {
if (!this.props.isAuthenticated) {
// redirect to login and add next param so we can redirect again after login
const redirectAfterLogin = this.props.location.pathname;
this.props.dispatch(pushState(null, `/signin?next=${redirectAfterLogin}`));
checkRedirect(props) {
if (!props.isAuthenticated) {
// redirect to login and add next param so we can redirect again after login
const redirectAfterLogin = props.location.pathname;
props.dispatch(pushState(null, `/signin?next=${redirectAfterLogin}`));
}
}
componentWillMount() {
this.checkRedirect(this.props);
}
componentWillReceiveProps(nextProps) {
this.checkRedirect(nextProps);
}
render() {
if (!this.props.isAuthenticated) {
return (<div className="panel">
<h2 className="text-danger">No anonymous access!</h2>
</div>)
const { isAuthenticated = false } = this.props;
if (isAuthenticated) {
// render the component that requires auth (passed to this wrapper)
return (
<Component {...this.props} />
)
}
// render the component that requires auth (passed to this wrapper)
return (
<Component {...this.props} />
)
return (<div className="panel">
<h2 className="text-danger">No anonymous access!</h2>
</div>);
}
}

View File

@@ -17,8 +17,7 @@ import { signOut } from '../actions/signOut';
export class HeaderLinks extends React.Component {
signOut(evt, key) {
debugger;
signOut();
this.props.dispatch(signOut());
}
render() {

View File

@@ -19,7 +19,7 @@ import {emailSignUpFormUpdate, emailSignUp} from '../../actions/signUp';
class EmailSignUpForm extends React.Component {
static propTypes = {
endpoint: PropTypes.string,
//endpoint: PropTypes.string,
inputProps: PropTypes.shape({
email: PropTypes.object,
password: PropTypes.object,
@@ -58,6 +58,7 @@ class EmailSignUpForm extends React.Component {
}
render () {
try {
const disabled = (
@@ -120,10 +121,10 @@ class EmailSignUpForm extends React.Component {
placeholder="Phone"
className="email-sign-up-email"
disabled={disabled}
value={read(this.props.auth, 'signUp.form.phone', '')}
errors={read(this.props.auth, 'signUp.errors.phone', {})}
onChange={this.handleInput.bind(this, "phone")}
{...this.props.inputProps.phone} />
value={read(this.props.auth, 'signUp.form.phoneNumber', '')}
errors={read(this.props.auth, 'signUp.errors.phoneNumber', {})}
onChange={this.handleInput.bind(this, "phoneNumber")}
{...this.props.inputProps.phoneNumber} />
<Input type="text"
label="Address 1"

View File

@@ -3,7 +3,6 @@
*/
import React, { PropTypes } from "react";
import { Input, Glyphicon } from "react-bootstrap";
import Immutable from "immutable";
class AuthInput extends React.Component {
static propTypes = {
@@ -15,7 +14,7 @@ class AuthInput extends React.Component {
static defaultProps = {
label: "",
value: null,
errors: Immutable.fromJS([])
errors: []
};
handleInput (ev) {

View File

@@ -9,7 +9,7 @@ export const customerInfoMap = ({
email, //: "arevinsky@gmail.com"
fname, //: "Andrew"
lname, //: "Revinsky"
phone, //: "+79031570864"
phoneNumber, //: "+79031570864"
state, //: "Kentucky"
zip //: "125315"
}) => ({
@@ -19,7 +19,7 @@ export const customerInfoMap = ({
},
email,
ssn,
"phoneNumber": phone,
"phoneNumber": phoneNumber,
"address": {
"street1": address1,
"street2": address2,

View File

@@ -10,14 +10,7 @@ const userInitalState = {
export const userReducer = (state = {...userInitalState}, action) => {
switch(action.type) {
case T.AUTH.AUTHENTICATE_COMPLETE: {
const { user } = action;
return {...state,
attributes: user,
isSignedIn: !!user
};
}
case T.AUTH.AUTHENTICATE_COMPLETE:
case T.AUTH.SIGN_IN_COMPLETE: {
const { user } = action;
return {...state,

View File

@@ -4,7 +4,9 @@
import fetch from './fetch';
import {
getEmailSignInUrl,
getEmailSignUpUrl
getEmailSignUpUrl,
getCurrentUserUrl,
getAccountsUrl
} from "./sessionStorage";
import root from './root';
@@ -31,4 +33,52 @@ export function apiSignUp(body) {
method: "post",
body: root.JSON.stringify(body)
}).then(parseResponse);
}
export function apiGetCurrentUser() {
return fetch(getCurrentUserUrl(), {
headers: {
"Accept": "application/json",
"Content-Type": "application/json"
},
method: "get"
}).then(parseResponse);
}
export function apiCreateAccount(customerId, title, initialBalance) {
//{
//"accountId": "0000015377cf131b-a250093f26850000"
//}
return fetch(getAccountsUrl(), {
headers: {
"Accept": "application/json",
"Content-Type": "application/json"
},
method: "post",
body: root.JSON.stringify({ customerId, title, initialBalance })
}).then(parseResponse);
}
export function apiRetrieveAccount(accountId) {
return fetch(`${getCurrentUserUrl()}/${accountId}`, {
headers: {
"Accept": "application/json",
"Content-Type": "application/json"
},
method: "get"
}).then(parseResponse);
}
export function apiRetrieveUsers(search) {
return fetch(getCurrentUserUrl(), {
headers: {
"Accept": "application/json",
"Content-Type": "application/json"
},
method: "get",
body: {
email: search
}
}).then(parseResponse);
}

View File

@@ -16,7 +16,8 @@ import {
setCurrentEndpoint,
setCurrentEndpointKey,
retrieveData,
persistData
persistData,
destroySession
} from "./sessionStorage";
// can't use "window" with node app
@@ -68,6 +69,11 @@ const defaultSettings = {
// save session configuration
export function applyConfig({ dispatch, endpoint={}, settings={}, reset=false } = {}) {
if (settings.currentLocation && settings.currentLocation.match(/blank=true/)) {
return Promise.resolve({blank: true});
}
let currentEndpointKey;
if (reset) {
@@ -80,6 +86,16 @@ export function applyConfig({ dispatch, endpoint={}, settings={}, reset=false }
setCurrentSettings({ ...defaultSettings, ...settings });
const currentHeaders = retrieveData(C.SAVED_CREDS_KEY) || {};
const accessToken = currentHeaders["access-token"];
//if (authRedirectHeaders && authRedirectHeaders["access-token"]) {
if (!accessToken) {
destroySession();
}
let { defaultEndpointKey, currentEndpoint } = parseEndpointConfig(
endpoint, getInitialEndpointKey()
);
@@ -92,26 +108,15 @@ export function applyConfig({ dispatch, endpoint={}, settings={}, reset=false }
setDefaultEndpointKey(defaultEndpointKey);
setCurrentEndpoint(currentEndpoint);
dispatch(setEndpointKeys(Object.keys(currentEndpoint), currentEndpointKey, defaultEndpointKey));
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." })
return Promise.resolve();
}

View File

@@ -4,4 +4,5 @@
export const INITIAL_CONFIG_KEY = "default";
export const DEFAULT_CONFIG_KEY = "defaultConfigKey";
export const SAVED_CONFIG_KEY = "currentConfigName";
export const SAVED_CREDS_KEY = "authHeaders";
export const SAVED_CREDS_KEY = "authHeaders";
export const SAVED_USER_INFO = "user-info";

View File

@@ -6,6 +6,24 @@ export function parseResponse (response) {
if (response.status >= 200 && response.status < 300) {
return json;
} else {
return json.then(err => Promise.reject(err));
//error: "Bad Request"
//exception: "org.springframework.web.bind.MethodArgumentNotValidException"
//message: "Validation failed for argument at index 0 in method: public rx.Observable<net.chrisrichardson.eventstore.javaexamples.banking.common.customers.CustomerResponse> net.chrisrichardson.eventstore.javaexamples.banking.web.commandside.customers.CustomerController.createCustomer(net.chrisrichardson.eventstore.javaexamples.banking.common.customers.CustomerInfo), with 3 error(s): [Field error in object 'customerInfo' on field 'ssn': rejected value [null]; codes [NotNull.customerInfo.ssn,NotNull.ssn,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [customerInfo.ssn,ssn]; arguments []; default message [ssn]]; default message [may not be null]] [Field error in object 'customerInfo' on field 'email': rejected value [null]; codes [NotNull.customerInfo.email,NotNull.email,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [customerInfo.email,email]; arguments []; default message [email]]; default message [may not be null]] [Field error in object 'customerInfo' on field 'phoneNumber': rejected value [null]; codes [NotNull.customerInfo.phoneNumber,NotNull.phoneNumber,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [customerInfo.phoneNumber,phoneNumber]; arguments []; default message [phoneNumber]]; default message [may not be null]] "
//path: "/customers"
//status: 400
//timestamp: 1458002123103
return json.then(({ message, ...rest }) => {
if (!message) {
return rest;
}
const jvmPattern = /\[Field error in object 'customerInfo' on field '(\w+)'/gm;
let errors = {};
message.replace(jvmPattern, (m, name) => {
errors[name] = ['Required'];
});
return { errors };
}).then(err => Promise.reject(err));
}
}

View File

@@ -12,7 +12,7 @@ import root from './root';
root.authState = {
currentSettings: {},
currentEndpoint: {},
defaultEndpointKey: null
defaultEndpointKey: 'default'
};
export function setCurrentSettings (s) {
@@ -67,7 +67,8 @@ export function resetConfig () {
export function destroySession () {
var sessionKeys = [
C.SAVED_CREDS_KEY,
C.SAVED_CONFIG_KEY
C.SAVED_CONFIG_KEY,
C.SAVED_USER_INFO
];
for (var key in sessionKeys) {
@@ -122,6 +123,14 @@ export function getEmailSignUpUrl (endpointKey) {
return `${getSessionEndpoint(endpointKey).emailRegistrationPath}`
}
export function getCurrentUserUrl (endpointKey) {
return `${getSessionEndpoint(endpointKey).currentUserPath}`
}
export function getAccountsUrl (endpointKey) {
return `${getSessionEndpoint(endpointKey).accountsPath}`
}
/**
* @deprecated
* @param key
@@ -136,6 +145,17 @@ export function getTokenFormat() {
return root.authState.currentSettings.tokenFormat;
}
export function persistUserData(user) {
persistData(C.SAVED_USER_INFO, user);
}
export function retrieveUserData() {
return retrieveData(C.SAVED_USER_INFO);
}
export function retrieveHeaders() {
return retrieveData(C.SAVED_CREDS_KEY) || {};
}
export function persistData (key, val) {
val = root.JSON.stringify(val);

View File

@@ -76,18 +76,30 @@ class MyAccounts extends React.Component {
//const deployTooltip = (<Tooltip>
// Create a new instance of this demo on your own Heroku server.
//</Tooltip>);
debugger;
const user = this.props.auth.user.attributes;
const {
email,
ssn,
name
email = '',
ssn = '',
name = {},
phoneNumber = '',
address,
toAccounts
} = user;
const {
firstName,
lastName
firstName = '',
lastName = ''
} = name;
const {
city,
state,
street1,
street2,
zipCode
} = address;
const { showAccountModal, show3rdPartyAccountModal, showDeleteAccountModal } = this.state;
const { accountToRemove = null } = this.state;
@@ -117,6 +129,11 @@ class MyAccounts extends React.Component {
<Col xs={8}><strong>{ email }</strong></Col>
</Row>
<Row>
<Col xs={4}>Phone:</Col>
<Col xs={8}><strong>{ phoneNumber }</strong></Col>
</Row>
<Row>
<Col xs={4}>SSN:</Col>
<Col xs={8}><strong>{ ssn }</strong></Col>

View File

@@ -22,20 +22,22 @@ import EmailSignInForm from "../controls/bootstrap/EmailSignInForm";
export class SignIn extends React.Component {
componentWillMount() {
}
componentWillReceiveProps(nextProps) {
if (nextProps.auth.user.isSignedIn) {
//debugger;
this.props.dispatch(pushState(null, nextProps.location.query.next));
checkRedirect(props) {
if (props.auth.user.isSignedIn) {
props.dispatch(pushState(null, props.location.query.next));
//// redirect to login and add next param so we can redirect again after login
//const redirectAfterLogin = this.props.location.pathname;
//this.props.dispatch(pushState(null, `/signin?next=${redirectAfterLogin}`));
}
}
componentWillMount() {
this.checkRedirect(this.props);
}
componentWillReceiveProps(nextProps) {
this.checkRedirect(nextProps);
}
render () {
const signInProps = {

View File

@@ -20,7 +20,7 @@ export class SignUp extends React.Component {
Register
</PageHeader>
<BS.Well>
<EmailSignUpForm endpoint="default" />
<EmailSignUpForm />
</BS.Well>
</div>
);