From 9d7274841d9e9d535e207b8b3e0399ecfd748e01 Mon Sep 17 00:00:00 2001 From: riteshsangwan Date: Fri, 23 Dec 2016 14:39:26 +0530 Subject: [PATCH 1/9] implement reset password --- config/default.js | 2 +- .../components/ResetPasswordView.jsx | 72 +++++++++++++++++++ .../components/ResetPasswordView.scss | 37 ++++++++++ .../containers/ResetPasswordContainer.js | 17 +++++ src/routes/ResetPassword/index.js | 20 ++++++ .../ResetPassword/modules/ResetPassword.js | 23 ++++++ src/routes/index.js | 3 +- src/services/APIService.js | 24 +++++++ 8 files changed, 196 insertions(+), 2 deletions(-) create mode 100644 src/routes/ResetPassword/components/ResetPasswordView.jsx create mode 100644 src/routes/ResetPassword/components/ResetPasswordView.scss create mode 100644 src/routes/ResetPassword/containers/ResetPasswordContainer.js create mode 100644 src/routes/ResetPassword/index.js create mode 100644 src/routes/ResetPassword/modules/ResetPassword.js diff --git a/config/default.js b/config/default.js index 6bc8230..7a496da 100644 --- a/config/default.js +++ b/config/default.js @@ -8,5 +8,5 @@ module.exports = { // below env variables are visible in frontend GOOGLE_API_KEY: process.env.GOOGLE_API_KEY || 'AIzaSyCrL-O319wNJK8kk8J_JAYsWgu6yo5YsDI', - API_BASE_PATH: process.env.API_BASE_PATH || 'https://kb-dsp-server-dev.herokuapp.com', + API_BASE_PATH: process.env.API_BASE_PATH || 'http://localhost:3500', }; diff --git a/src/routes/ResetPassword/components/ResetPasswordView.jsx b/src/routes/ResetPassword/components/ResetPasswordView.jsx new file mode 100644 index 0000000..bf07f2f --- /dev/null +++ b/src/routes/ResetPassword/components/ResetPasswordView.jsx @@ -0,0 +1,72 @@ +import React, {Component} from 'react'; +import CSSModules from 'react-css-modules'; +import styles from './ResetPasswordView.scss'; +import TextField from '../../../components/TextField'; +import FormField from '../../../components/FormField'; +import Button from '../../../components/Button'; +import {reduxForm} from 'redux-form'; +import {sendRequest} from '../modules/ResetPassword'; + +class ResetPasswordView extends Component { + + /** + * This function is called when the form is submitted + * This is triggered by handleSubmit + */ + onSubmit(data) { + return sendRequest(data); + } + + render() { + const {fields, handleSubmit, location: {query: {token}}} = this.props; + const _self = this; + return ( +
+
_self.onSubmit({...data, code: token}))}> +
+ + + + +
+
+ + + + +
+ + {/* add-package end */} +
+ +
+
+ {/* form end */} +
+ ); + } +} + +ResetPasswordView.propTypes = { + fields: React.PropTypes.object.isRequired, + location: React.PropTypes.object.isRequired, + handleSubmit: React.PropTypes.func.isRequired, +}; + +const form = reduxForm({ + form: 'resetPasswordForm', + fields: ['password', 'email'], + validate(values) { + const errors = {}; + if (!values.password) { + errors.password = 'required'; + } + if (!values.email) { + errors.email = 'required'; + } + + return errors; + }, +}); + +export default form(CSSModules(ResetPasswordView, styles)); diff --git a/src/routes/ResetPassword/components/ResetPasswordView.scss b/src/routes/ResetPassword/components/ResetPasswordView.scss new file mode 100644 index 0000000..54a78d9 --- /dev/null +++ b/src/routes/ResetPassword/components/ResetPasswordView.scss @@ -0,0 +1,37 @@ +.reset-password-form { + padding: 50px 0 14px; + margin: 0 300px; + height: calc(100vh - 60px - 42px - 50px); // header height - breadcrumb height - footer height + h4 { + font-weight: bold; + font-size: 20px; + color: #525051; + margin-top: 40px; + border-top: 1px solid #e7e8ea; + padding-top: 25px; + } +} +.row { + display: flex; + margin-bottom: 22px; + label { + display: block; + flex: 0 0 20%; + align-self: center; + font-size: 14px; + color: #343434; + font-weight: bold; + } + + .input-with-label { + flex: 0 0 20%; + display: flex; + align-items: center; + .input { + flex: 0 0 66%; + } + } +} +.actions { + text-align: right; +} \ No newline at end of file diff --git a/src/routes/ResetPassword/containers/ResetPasswordContainer.js b/src/routes/ResetPassword/containers/ResetPasswordContainer.js new file mode 100644 index 0000000..6abbe02 --- /dev/null +++ b/src/routes/ResetPassword/containers/ResetPasswordContainer.js @@ -0,0 +1,17 @@ +import {asyncConnect} from 'redux-connect'; +import {actions} from '../modules/ResetPassword'; +import {browserHistory} from 'react-router'; + +import ResetPasswordView from '../components/ResetPasswordView'; + +const resolve = [{ + promise: () => Promise.resolve(), +}]; + +const handleSuccess = () => { + browserHistory.push('/'); +}; + +const mapState = (state) => ({...state.resetPassword, onSubmitSuccess: handleSuccess}); + +export default asyncConnect(resolve, mapState, actions)(ResetPasswordView); diff --git a/src/routes/ResetPassword/index.js b/src/routes/ResetPassword/index.js new file mode 100644 index 0000000..ff64d00 --- /dev/null +++ b/src/routes/ResetPassword/index.js @@ -0,0 +1,20 @@ +import {injectReducer} from '../../store/reducers'; + +export default (store) => ({ + path: 'reset-password', + name: 'Reset password', /* Breadcrumb name */ + staticName: true, + getComponent(nextState, cb) { + require.ensure([], (require) => { + const Dashboard = require('./containers/ResetPasswordContainer').default; + const reducer = require('./modules/ResetPassword').default; + + injectReducer(store, {key: 'resetPassword', reducer}); + if (!nextState.location.query.token) { + cb(new Error('Invalid route invokation')); + } else { + cb(null, Dashboard); + } + }, 'ResetPassword'); + }, +}); diff --git a/src/routes/ResetPassword/modules/ResetPassword.js b/src/routes/ResetPassword/modules/ResetPassword.js new file mode 100644 index 0000000..a5bcb9e --- /dev/null +++ b/src/routes/ResetPassword/modules/ResetPassword.js @@ -0,0 +1,23 @@ +import {handleActions} from 'redux-actions'; +import APIService from 'services/APIService'; +// ------------------------------------ +// Actions +// ------------------------------------ + +export const actions = { +}; + +export const sendRequest = (values) => new Promise((resolve, reject) => { + APIService.resetPassword(values).then((result) => { + resolve(result); + }).catch((reason) => { + reject(reason); + }); +}); + +// ------------------------------------ +// Reducer +// ------------------------------------ +export default handleActions({ +}, { +}); diff --git a/src/routes/index.js b/src/routes/index.js index ba238aa..41c9800 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -19,6 +19,7 @@ import BrowseProviderRoute from './BrowseProvider'; import DroneDetailsRoute from './DroneDetails'; import AvailablePackagesRoute from './AvailablePackages'; import ProviderDetailsRoute from './ProviderDetails'; +import ResetPasswordRoute from './ResetPassword'; export const createRoutes = (store) => ({ path: '/', @@ -53,7 +54,7 @@ export const createRoutes = (store) => ({ DroneDetailsRoute(store), AvailablePackagesRoute(store), ProviderDetailsRoute(store), - + ResetPasswordRoute(store), ], }); diff --git a/src/services/APIService.js b/src/services/APIService.js index a69c8d2..391d037 100644 --- a/src/services/APIService.js +++ b/src/services/APIService.js @@ -578,4 +578,28 @@ export default class APIService { .query(params) .end(); } + + /** + * Reset the user password + * @param {Object} entity the client request payload + */ + static resetPassword(entity) { + return request + .post(`${config.API_BASE_PATH}/api/v1/reset-password`) + .set('Content-Type', 'application/json') + .send(entity) + .end(); + } + + /** + * Send the forgot password link to user's email account + * @param {Object} entity the client request payload + */ + static forgotPassword(entity) { + return request + .post(`${config.API_BASE_PATH}/api/v1/forgot-password`) + .set('Content-Type', 'application/json') + .send(entity) + .end(); + } } From e111cfb7da0f88f44b34edaef191fe74c2a976ed Mon Sep 17 00:00:00 2001 From: riteshsangwan Date: Fri, 23 Dec 2016 15:37:58 +0530 Subject: [PATCH 2/9] form field error styling --- .../ResetPassword/components/ResetPasswordView.jsx | 4 ++-- .../ResetPassword/components/ResetPasswordView.scss | 13 ++++++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/routes/ResetPassword/components/ResetPasswordView.jsx b/src/routes/ResetPassword/components/ResetPasswordView.jsx index bf07f2f..aacbd09 100644 --- a/src/routes/ResetPassword/components/ResetPasswordView.jsx +++ b/src/routes/ResetPassword/components/ResetPasswordView.jsx @@ -25,13 +25,13 @@ class ResetPasswordView extends Component {
_self.onSubmit({...data, code: token}))}>
- +
- +
diff --git a/src/routes/ResetPassword/components/ResetPasswordView.scss b/src/routes/ResetPassword/components/ResetPasswordView.scss index 54a78d9..b008991 100644 --- a/src/routes/ResetPassword/components/ResetPasswordView.scss +++ b/src/routes/ResetPassword/components/ResetPasswordView.scss @@ -10,6 +10,17 @@ border-top: 1px solid #e7e8ea; padding-top: 25px; } + :global { + .form-field { + width: 100%; + &.error { + color: #ff3100; + > div:first-child { + border: 1px solid #ff3100; + } + } + } + } } .row { display: flex; @@ -34,4 +45,4 @@ } .actions { text-align: right; -} \ No newline at end of file +} From 3c5586a63c1e49cbaba9e69e877eb492fdbc449d Mon Sep 17 00:00:00 2001 From: riteshsangwan Date: Fri, 23 Dec 2016 19:01:30 +0530 Subject: [PATCH 3/9] forgot password UI --- .../Home/components/LoginModal/LoginModal.jsx | 175 +++++++++++------- 1 file changed, 113 insertions(+), 62 deletions(-) diff --git a/src/routes/Home/components/LoginModal/LoginModal.jsx b/src/routes/Home/components/LoginModal/LoginModal.jsx index 5bd807e..95d0761 100644 --- a/src/routes/Home/components/LoginModal/LoginModal.jsx +++ b/src/routes/Home/components/LoginModal/LoginModal.jsx @@ -7,6 +7,9 @@ import Button from 'components/Button'; import Checkbox from 'components/Checkbox'; import TextField from 'components/TextField'; import styles from './LoginModal.scss'; +import APIService from '../../../../services/APIService'; +import {toastr} from 'react-redux-toastr'; +import {browserHistory} from 'react-router'; /* * customStyles */ @@ -50,10 +53,11 @@ FormField.propTypes = { */ class LogInModal extends React.Component { - constructor() { - super(); + constructor(props) { + super(props); this.state = { modalLoginIsOpen: false, + showForgetPassword: false, }; } @@ -62,7 +66,7 @@ class LogInModal extends React.Component { } closeLoginModal() { - this.setState({modalLoginIsOpen: false}); + this.setState({modalLoginIsOpen: false, showForgetPassword: false}); } login() { @@ -81,9 +85,27 @@ class LogInModal extends React.Component { }, 100); } + forgetPassword() { + this.setState({showForgetPassword: true}); + } + + /** + * This method is invoked when reset password request is submitted + */ + handleForgetPassword(data) { + APIService.forgotPassword({email: data.emailUp}).then((result) => { + toastr.success('', 'Reset password link emailed to your email address'); + this.closeLoginModal(); + }).catch((reason) => { + const message = reason.response.body.error || 'something went wrong, please try again'; + toastr.error(`${message}`); + this.closeLoginModal(); + }); + } + render() { + const _self = this; const {handleSubmit, fields, handleLoggedIn, loggedUser, hasError, errorText} = this.props; - return (
@@ -100,71 +122,88 @@ class LogInModal extends React.Component {
-
Login to Your Account
+ {this.state.showForgetPassword === false &&
Login to Your Account
} + {this.state.showForgetPassword === true &&
Reset forgotten password
}
+ {this.state.showForgetPassword === false && + + - - - - - {/* login with end */} -
-
-
or
-
-
- {/* or end */} -
- {hasError && {errorText.error}} -
- - - + + {/* login with end */} +
+
+
or
+
+ {/* or end */}
- - - + {hasError && {errorText.error}} +
+ + + +
+
+ + + +
-
- {/* input end */} -
-
- this.props.fields.remember.onChange(!this.props.fields.remember.value)} - id="remember" + {/* input end */} +
+
+ this.props.fields.remember.onChange(!this.props.fields.remember.value)} + id="remember" + > + Remember me + +
+ +
+
+ +
+
+ Don’t have an account? Sign Up
- -
-
- -
-
- Don’t have an account? Sign Up -
- + + } + { this.state.showForgetPassword === true && +
_self.handleForgetPassword(data))}> +
+ {hasError && {errorText.error}} +
+ + + +
+
+
+ +
+
+ } - -
); } @@ -181,8 +220,20 @@ LogInModal.propTypes = { const fields = ['remember', 'email', 'password', 'emailUp', 'passwordUp']; -const validate = (values) => { +const validate = (values, props) => { const errors = {}; + if (!values.emailUp && !values.email) { + errors.emailUp = 'Email is required'; + } else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.emailUp) && !values.email) { + errors.emailUp = 'Invalid email address'; + } + + if (errors.emailUp && (values.emailUp || values.email)) { + return errors; + } else if (values.emailUp) { + return errors; + } + if (!values.email) { errors.email = 'Email is required'; } else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) { From 5b0654a56dfdad341080ed99ef71636471a70b2a Mon Sep 17 00:00:00 2001 From: riteshsangwan Date: Fri, 23 Dec 2016 19:26:21 +0530 Subject: [PATCH 4/9] WIP add auth service --- src/services/AuthService.js | 79 +++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 src/services/AuthService.js diff --git a/src/services/AuthService.js b/src/services/AuthService.js new file mode 100644 index 0000000..333cf6f --- /dev/null +++ b/src/services/AuthService.js @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2016 Topcoder Inc, All rights reserved. + */ + +/** + * auth0 Authentication service for the app. + * + * @author TCSCODER + * @version 1.0.0 + */ + +import Auth0Lock from 'auth0-lock' +import config from 'config/default'; +import UserApi from 'api/UserApi'; + +const userApi = new UserApi(config.api.basePath); + +export default class AuthService { + + constructor(clientId, domain) { + // Configure Auth0 + this.lock = new Auth0Lock(clientId, domain, { + allowedConnections: ['google-oauth2', 'facebook', 'github'], + autoclose: true + }); + // Add callback for lock `authenticated` event + this.lock.on('authenticated', this._doAuthentication.bind(this)); + // binds login functions to keep this context + this.login = this.login.bind(this); + } + + _doAuthentication(authResult, a, b, c) { + // Saves the user token + this.setToken(authResult.idToken); + var that = this; + this.lock.getProfile(authResult.idToken, function(error, profile) { + if (error) { + console.log(error); + return; + } + userApi.registerSocialUser(profile.name, profile.email).then((authResult) => { + that.setToken(authResult.accessToken); + }).catch((err) => { + console.error(err); + }); + }); + } + + login() { + // Call the show method to display the widget. + this.lock.show() + } + + loggedIn() { + // Checks if there is a saved token and it's still valid + return !!this.getToken(); + } + + setToken(idToken) { + // Saves user token to sessionStorage + sessionStorage.setItem('id_token', idToken); + } + + getToken() { + // Retrieves the user token from sessionStorage + return sessionStorage.getItem('id_token'); + } + + logout() { + // Clear user token and profile data from sessionStorage + sessionStorage.removeItem('id_token'); + } + + getHeader() { + return { + Authorization: `Bearer ${this.getToken()}` + } + } +} \ No newline at end of file From d7d8bfcb684582a67d91f187d33e67acdbca8853 Mon Sep 17 00:00:00 2001 From: riteshsangwan Date: Fri, 23 Dec 2016 21:58:01 +0530 Subject: [PATCH 5/9] implement social auth0 login --- config/default.js | 3 + package.json | 12 +- src/api/User.js | 5 +- src/config/index.js | 23 --- .../Home/components/LoginModal/LoginModal.jsx | 39 ++++- .../components/SignupModal/SignupModal.jsx | 32 +++- src/routes/index.js | 12 +- src/services/AuthService.js | 147 +++++++++++++----- src/store/modules/global.js | 4 +- 9 files changed, 195 insertions(+), 82 deletions(-) delete mode 100644 src/config/index.js diff --git a/config/default.js b/config/default.js index 7a496da..0ae3567 100644 --- a/config/default.js +++ b/config/default.js @@ -9,4 +9,7 @@ module.exports = { // below env variables are visible in frontend GOOGLE_API_KEY: process.env.GOOGLE_API_KEY || 'AIzaSyCrL-O319wNJK8kk8J_JAYsWgu6yo5YsDI', API_BASE_PATH: process.env.API_BASE_PATH || 'http://localhost:3500', + REACT_APP_AUTH0_CLIENT_ID: process.env.REACT_APP_AUTH0_CLIENT_ID || 'h7p6V93Shau3SSvqGrl6V4xrATlkrVGm', + REACT_APP_AUTH0_CLIENT_DOMAIN: process.env.REACT_APP_AUTH0_CLIENT_DOMAIN || 'spanhawk.auth0.com', + AUTH0_CALLBACK: 'http://localhost:3000', }; diff --git a/package.json b/package.json index 2ee9179..56a0a6a 100644 --- a/package.json +++ b/package.json @@ -55,18 +55,18 @@ "rc-tooltip": "^3.4.2", "react": "^15.3.2", "react-breadcrumbs": "^1.5.1", - "react-count-down": "^1.0.3", "react-click-outside": "^2.2.0", + "react-count-down": "^1.0.3", "react-css-modules": "^3.7.10", "react-date-picker": "^5.3.28", "react-dom": "^15.3.2", + "react-dropdown": "^1.2.0", "react-flexbox-grid": "^0.10.2", "react-google-maps": "^6.0.1", - "react-modal": "^1.5.2", - "react-dropdown": "^1.2.0", + "react-highcharts": "^11.0.0", "react-icheck": "^0.3.6", "react-input-range": "^0.9.3", - "react-highcharts": "^11.0.0", + "react-modal": "^1.5.2", "react-redux": "^4.0.0", "react-redux-toastr": "^4.2.2", "react-router": "^2.8.1", @@ -75,8 +75,8 @@ "react-simple-dropdown": "^1.1.5", "react-slick": "^0.14.5", "react-star-rating-component": "^1.2.2", - "react-timeago": "^3.1.3", "react-tabs": "^0.8.2", + "react-timeago": "^3.1.3", "reactable": "^0.14.1", "redbox-react": "^1.2.10", "redux": "^3.0.0", @@ -86,8 +86,8 @@ "redux-logger": "^2.6.1", "redux-thunk": "^2.0.0", "sass-loader": "^4.0.0", - "socket.io-client": "^1.7.1", "slick-carousel": "^1.6.0", + "socket.io-client": "^1.7.1", "style-loader": "^0.13.0", "superagent": "^2.3.0", "superagent-promise": "^1.1.0", diff --git a/src/api/User.js b/src/api/User.js index 70c1728..a12bc57 100644 --- a/src/api/User.js +++ b/src/api/User.js @@ -56,7 +56,7 @@ class UserApi { })}); } - registerSocialUser(name, email) { + registerSocialUser(name, email, token) { const url = `${this.basePath}/api/v1/login/social`; return reqwest({ @@ -64,6 +64,9 @@ class UserApi { method: 'post', type: 'json', contentType: 'application/json', + headers: { + Authorization: `Bearer ${token}`, + }, data: JSON.stringify({ name, email, diff --git a/src/config/index.js b/src/config/index.js deleted file mode 100644 index c6b575d..0000000 --- a/src/config/index.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright (c) 2016 Topcoder Inc, All rights reserved. - */ - -/** - * Webapp configuration - * - * @author TCSCODER - * @version 1.0.0 - */ - -const config = { - api: { - basePath: process.env.REACT_APP_API_BASE_PATH || 'http://localhost:3500', - }, - socket: { - url: process.env.REACT_APP_SOCKET_URL || 'http://localhost:3500', - }, - AUTH0_CLIEND_ID: process.env.REACT_APP_AUTH0_CLIEND_ID || '3CGKzjS2nVSqHxHHE64RhvvKY6e0TYpK', - AUTH0_DOMAIN: process.env.REACT_APP_AUTH0_DOMAIN || 'dronetest.auth0.com', -}; - -export default config; diff --git a/src/routes/Home/components/LoginModal/LoginModal.jsx b/src/routes/Home/components/LoginModal/LoginModal.jsx index 95d0761..cdcc922 100644 --- a/src/routes/Home/components/LoginModal/LoginModal.jsx +++ b/src/routes/Home/components/LoginModal/LoginModal.jsx @@ -9,7 +9,8 @@ import TextField from 'components/TextField'; import styles from './LoginModal.scss'; import APIService from '../../../../services/APIService'; import {toastr} from 'react-redux-toastr'; -import {browserHistory} from 'react-router'; +import {defaultAuth0Service} from '../../../../services/AuthService'; + /* * customStyles */ @@ -89,16 +90,42 @@ class LogInModal extends React.Component { this.setState({showForgetPassword: true}); } + /** + * Login using google social network, + * this method internally uses auth0 service + */ + googleLogin() { + defaultAuth0Service.login({connection: 'google-oauth2'}, (error) => { + if (error) { + const message = error.message || 'something went wrong, please try again'; + toastr.error(message); + } + }); + } + + /** + * Login using facebook social network, + * this method internally uses auth0 service + */ + facebookLogin() { + defaultAuth0Service.login({connection: 'facebook'}, (error) => { + if (error) { + const message = error.message || 'something went wrong, please try again'; + toastr.error(message); + } + }); + } + /** * This method is invoked when reset password request is submitted */ handleForgetPassword(data) { - APIService.forgotPassword({email: data.emailUp}).then((result) => { + APIService.forgotPassword({email: data.emailUp}).then(() => { toastr.success('', 'Reset password link emailed to your email address'); this.closeLoginModal(); }).catch((reason) => { const message = reason.response.body.error || 'something went wrong, please try again'; - toastr.error(`${message}`); + toastr.error(message); this.closeLoginModal(); }); } @@ -128,14 +155,14 @@ class LogInModal extends React.Component { {this.state.showForgetPassword === false &&
- + Log In with Google Plus @@ -220,7 +247,7 @@ LogInModal.propTypes = { const fields = ['remember', 'email', 'password', 'emailUp', 'passwordUp']; -const validate = (values, props) => { +const validate = (values) => { const errors = {}; if (!values.emailUp && !values.email) { errors.emailUp = 'Email is required'; diff --git a/src/routes/Home/components/SignupModal/SignupModal.jsx b/src/routes/Home/components/SignupModal/SignupModal.jsx index 213d6e6..f6a97e3 100644 --- a/src/routes/Home/components/SignupModal/SignupModal.jsx +++ b/src/routes/Home/components/SignupModal/SignupModal.jsx @@ -6,6 +6,8 @@ import Modal from 'react-modal'; import Button from 'components/Button'; import TextField from 'components/TextField'; import styles from './SignupModal.scss'; +import {defaultAuth0Service} from '../../../../services/AuthService'; +import {toastr} from 'react-redux-toastr'; /* * customStyles @@ -79,6 +81,32 @@ class SignupModal extends React.Component { }, 100); } + /** + * Login using google social network, + * this method internally uses auth0 service + */ + googleLogin() { + defaultAuth0Service.login({connection: 'google-oauth2'}, (error) => { + if (error) { + const message = error.message || 'something went wrong, please try again'; + toastr.error(message); + } + }); + } + + /** + * Login using facebook social network, + * this method internally uses auth0 service + */ + facebookLogin() { + defaultAuth0Service.login({connection: 'facebook'}, (error) => { + if (error) { + const message = error.message || 'something went wrong, please try again'; + toastr.error(message); + } + }); + } + render() { const {handleSubmit, fields, handleSigned, signedUser, hasError, errorText} = this.props; @@ -103,14 +131,14 @@ class SignupModal extends React.Component {
- + Sign Up with Google Plus diff --git a/src/routes/index.js b/src/routes/index.js index 41c9800..b689d66 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -20,6 +20,7 @@ import DroneDetailsRoute from './DroneDetails'; import AvailablePackagesRoute from './AvailablePackages'; import ProviderDetailsRoute from './ProviderDetails'; import ResetPasswordRoute from './ResetPassword'; +import {defaultAuth0Service} from '../services/AuthService'; export const createRoutes = (store) => ({ path: '/', @@ -28,8 +29,15 @@ export const createRoutes = (store) => ({ component: CoreLayout, indexRoute: { onEnter: (nextState, replace, cb) => { - replace('/dashboard'); - cb(); + // parse the hash if present + if (nextState.location.hash) { + defaultAuth0Service.parseHash(nextState.location.hash); + replace('/dashboard'); + cb(); + } else { + replace('/dashboard'); + cb(); + } }, }, childRoutes: [ diff --git a/src/services/AuthService.js b/src/services/AuthService.js index 333cf6f..b28af64 100644 --- a/src/services/AuthService.js +++ b/src/services/AuthService.js @@ -1,7 +1,7 @@ /** * Copyright (c) 2016 Topcoder Inc, All rights reserved. */ - +/* eslint no-console: 0 */ /** * auth0 Authentication service for the app. * @@ -9,71 +9,138 @@ * @version 1.0.0 */ -import Auth0Lock from 'auth0-lock' -import config from 'config/default'; -import UserApi from 'api/UserApi'; +import Auth0 from 'auth0-js'; +import config from '../../config/default'; +import UserApi from '../api/User'; + -const userApi = new UserApi(config.api.basePath); +const userApi = new UserApi(config.API_BASE_PATH); -export default class AuthService { +class AuthService { + /** + * Default constructor + * @param {String} clientId the auth0 client id + * @param {String} domain the auth0 domain + */ constructor(clientId, domain) { - // Configure Auth0 - this.lock = new Auth0Lock(clientId, domain, { - allowedConnections: ['google-oauth2', 'facebook', 'github'], - autoclose: true + this.auth0 = new Auth0({ + clientID: clientId, + domain, + responseType: 'token', + callbackURL: config.AUTH0_CALLBACK, }); - // Add callback for lock `authenticated` event - this.lock.on('authenticated', this._doAuthentication.bind(this)); - // binds login functions to keep this context this.login = this.login.bind(this); + this.parseHash = this.parseHash.bind(this); + this.loggedIn = this.loggedIn.bind(this); + this.logout = this.logout.bind(this); + this.getProfile = this.getProfile.bind(this); + this.getHeader = this.getHeader.bind(this); } - _doAuthentication(authResult, a, b, c) { - // Saves the user token - this.setToken(authResult.idToken); - var that = this; - this.lock.getProfile(authResult.idToken, function(error, profile) { - if (error) { - console.log(error); - return; - } - userApi.registerSocialUser(profile.name, profile.email).then((authResult) => { - that.setToken(authResult.accessToken); - }).catch((err) => { - console.error(err); - }); - }); + /** + * Redirects the user to appropriate social network for oauth2 authentication + * + * @param {Object} params any params to pass to auth0 client + * @param {Function} onError function to execute on error + */ + login(params, onError) { + // redirects the call to auth0 instance + this.auth0.login(params, onError); } - login() { - // Call the show method to display the widget. - this.lock.show() + /** + * Parse the hash fragment of url + * This method will actually parse the token + * will create a user profile if not already present and save the id token in local storage + * if there is some error delete the access token + * @param {String} hash the hash fragment + */ + parseHash(hash) { + const _self = this; + const authResult = _self.auth0.parseHash(hash); + if (authResult && authResult.idToken) { + _self.setToken(authResult.idToken); + // get social profile + _self.getProfile((error, profile) => { + if (error) { + // remove the id token + _self.removeToken(); + throw error; + } else { + userApi.registerSocialUser(profile.name, profile.email, _self.getToken()).then((loginResult) => { + console.log('user registered successfully', loginResult); + }).catch((reason) => { + // remove the id token + _self.removeToken(); + throw reason; + }); + } + }); + } } + /** + * Check if the user is logged in + * @param {String} hash the hash fragment + */ loggedIn() { // Checks if there is a saved token and it's still valid return !!this.getToken(); } + /** + * Set the id token to be stored in local storage + * @param {String} idToken the token to store + */ setToken(idToken) { - // Saves user token to sessionStorage - sessionStorage.setItem('id_token', idToken); + // Saves user token to localStorage + localStorage.setItem('id_token', idToken); } + /** + * Get the stored id token from local storage + */ getToken() { - // Retrieves the user token from sessionStorage - return sessionStorage.getItem('id_token'); + // Retrieves the user token from localStorage + return localStorage.getItem('id_token'); } + /** + * Remove the id token from local storage + */ + removeToken() { + // Clear user token and profile data from localStorage + localStorage.removeItem('id_token'); + } + + /** + * Logout the user from the application, delete the id token + */ logout() { - // Clear user token and profile data from sessionStorage - sessionStorage.removeItem('id_token'); + this.removeToken(); } + /** + * Get the authorization header for API access + */ getHeader() { return { - Authorization: `Bearer ${this.getToken()}` - } + Authorization: `Bearer ${this.getToken()}`, + }; } -} \ No newline at end of file + + /** + * Get the profile of currently logged in user + * + * @param {callback} the callback function to call after operation finishes + * @return {Object} the profile of logged in user + */ + getProfile(callback) { + this.auth0.getProfile(this.getToken(), callback); + } +} + +const defaultAuth0Service = new AuthService(config.REACT_APP_AUTH0_CLIENT_ID, config.REACT_APP_AUTH0_CLIENT_DOMAIN); + +export {AuthService as default, defaultAuth0Service}; diff --git a/src/store/modules/global.js b/src/store/modules/global.js index 0e0357c..6768e8f 100644 --- a/src/store/modules/global.js +++ b/src/store/modules/global.js @@ -1,9 +1,9 @@ import {handleActions, createAction} from 'redux-actions'; import {browserHistory} from 'react-router'; import UserApi from 'api/User.js'; -import config from '../../config'; +import config from '../../../config/default'; -const userApi = new UserApi(config.api.basePath); +const userApi = new UserApi(config.API_BASE_PATH); // ------------------------------------ // Actions From 0c3e1cb77d7fe7575ac1506801e39cc6b12b02d2 Mon Sep 17 00:00:00 2001 From: riteshsangwan Date: Sat, 24 Dec 2016 04:29:34 +0530 Subject: [PATCH 6/9] update readme --- README.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fc1ed0e..6221aec 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,27 @@ See Guild https://github.com/lorenwest/node-config/wiki/Configuration-Files |`PORT`| The port to listen| |`GOOGLE_API_KEY`| The google api key see (https://developers.google.com/maps/documentation/javascript/get-api-key#key)| |`API_BASE_URL`| The base URL for Drone API | - +|`REACT_APP_AUTH0_CLIENT_ID`| The auth0 client id | +|`REACT_APP_AUTH0_CLIENT_DOMAIN`| The auth0 client domain | + +### Auth0 setup +- Create an account on auth0. +- Click on clients in left side menu, it will redirect you to client page. Click on CREATE CLIENT button + to create a new client. +- Copy the client id and client domain and export them as environment variables. +- Add `http://localhost:3000` as Allowed callback url's in client settings. + +### Add social connections + +### Facebook social connection +- To add facebook social connection to auth0, you have to create a facebook app. + Go to facebook [developers](https://developers.facebook.com/apps) and create a new app. +- Copy the app secret and app id to auth0 social connections facebook tab. +- You have to setup the oauth2 callback in app oauth settings. +- For more information visit auth0 [docs](https://auth0.com/docs/connections/social/facebook) + +### Google social connection +- For more information on how to connect google oauth2 client, visit official [docs](https://auth0.com/docs/connections/social/google) ## Install dependencies `npm i` From 83bbba2283489a02b883c655b45db23c2d20159c Mon Sep 17 00:00:00 2001 From: riteshsangwan Date: Wed, 28 Dec 2016 21:17:32 +0530 Subject: [PATCH 7/9] fix some issues --- config/default.js | 6 +----- src/components/TextField/TextField.scss | 1 + src/config/index.js | 14 ++++++++++++++ src/routes/DronesMap/modules/DronesMap.js | 4 ++-- .../Home/components/LoginModal/LoginModal.jsx | 6 ++++-- .../components/ResetPasswordView.jsx | 12 ++++++++++-- .../containers/ResetPasswordContainer.js | 7 +------ src/routes/ResetPassword/index.js | 2 +- src/services/APIService.js | 2 +- src/services/AuthService.js | 16 ++++++++-------- src/store/modules/global.js | 2 +- 11 files changed, 44 insertions(+), 28 deletions(-) create mode 100644 src/config/index.js diff --git a/config/default.js b/config/default.js index 0ae3567..d35b608 100644 --- a/config/default.js +++ b/config/default.js @@ -1,6 +1,6 @@ /* eslint-disable import/no-commonjs */ /** - * Main config file + * Main config file for the server which is hosting the reat app */ module.exports = { // below env variables are NOT visible in frontend @@ -8,8 +8,4 @@ module.exports = { // below env variables are visible in frontend GOOGLE_API_KEY: process.env.GOOGLE_API_KEY || 'AIzaSyCrL-O319wNJK8kk8J_JAYsWgu6yo5YsDI', - API_BASE_PATH: process.env.API_BASE_PATH || 'http://localhost:3500', - REACT_APP_AUTH0_CLIENT_ID: process.env.REACT_APP_AUTH0_CLIENT_ID || 'h7p6V93Shau3SSvqGrl6V4xrATlkrVGm', - REACT_APP_AUTH0_CLIENT_DOMAIN: process.env.REACT_APP_AUTH0_CLIENT_DOMAIN || 'spanhawk.auth0.com', - AUTH0_CALLBACK: 'http://localhost:3000', }; diff --git a/src/components/TextField/TextField.scss b/src/components/TextField/TextField.scss index 46a04ab..56f5f52 100644 --- a/src/components/TextField/TextField.scss +++ b/src/components/TextField/TextField.scss @@ -2,6 +2,7 @@ width: 100%; border: 1px solid #ebebeb; + input[type="password"], input[type="text"] { width: 100%; padding: 0 10px; diff --git a/src/config/index.js b/src/config/index.js new file mode 100644 index 0000000..db5b4f9 --- /dev/null +++ b/src/config/index.js @@ -0,0 +1,14 @@ +/* eslint-disable import/no-commonjs */ +/** + * Main config file for the react app + */ +module.exports = { + // below env variables are visible in frontend + API_BASE_PATH: process.env.API_BASE_PATH || 'http://localhost:3500', + REACT_APP_AUTH0_CLIENT_ID: process.env.REACT_APP_AUTH0_CLIENT_ID || 'h7p6V93Shau3SSvqGrl6V4xrATlkrVGm', + REACT_APP_AUTH0_CLIENT_DOMAIN: process.env.REACT_APP_AUTH0_CLIENT_DOMAIN || 'spanhawk.auth0.com', + AUTH0_CALLBACK: 'http://localhost:3000', + socket: { + url: process.env.REACT_APP_SOCKET_URL || 'http://localhost:3500', + }, +}; diff --git a/src/routes/DronesMap/modules/DronesMap.js b/src/routes/DronesMap/modules/DronesMap.js index 71ac81b..eb8da53 100644 --- a/src/routes/DronesMap/modules/DronesMap.js +++ b/src/routes/DronesMap/modules/DronesMap.js @@ -1,7 +1,7 @@ import {handleActions} from 'redux-actions'; import io from 'socket.io-client'; import APIService from 'services/APIService'; -import config from '../../../../config/default'; +import config from '../../../config'; // Drones will be updated and map will be redrawn every 3s // Otherwise if drones are updated with high frequency (e.g. 0.5s), the map will be freezing @@ -32,7 +32,7 @@ export const init = () => async(dispatch) => { const {body: {items: drones}} = await APIService.searchDrones({limit: DRONE_LIMIT}); lastUpdated = new Date().getTime(); dispatch({type: DRONES_LOADED, payload: {drones}}); - socket = io(config.API_BASE_PATH); + socket = io(config.socket.url); socket.on('dronepositionupdate', (drone) => { pendingUpdates[drone.id] = drone; if (updateTimeoutId) { diff --git a/src/routes/Home/components/LoginModal/LoginModal.jsx b/src/routes/Home/components/LoginModal/LoginModal.jsx index cdcc922..e912c20 100644 --- a/src/routes/Home/components/LoginModal/LoginModal.jsx +++ b/src/routes/Home/components/LoginModal/LoginModal.jsx @@ -11,6 +11,8 @@ import APIService from '../../../../services/APIService'; import {toastr} from 'react-redux-toastr'; import {defaultAuth0Service} from '../../../../services/AuthService'; +const EMAIL_REGEX = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i; + /* * customStyles */ @@ -251,7 +253,7 @@ const validate = (values) => { const errors = {}; if (!values.emailUp && !values.email) { errors.emailUp = 'Email is required'; - } else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.emailUp) && !values.email) { + } else if (!EMAIL_REGEX.test(values.emailUp) && !values.email) { errors.emailUp = 'Invalid email address'; } @@ -263,7 +265,7 @@ const validate = (values) => { if (!values.email) { errors.email = 'Email is required'; - } else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) { + } else if (!EMAIL_REGEX.test(values.email)) { errors.email = 'Invalid email address'; } if (!values.password) { diff --git a/src/routes/ResetPassword/components/ResetPasswordView.jsx b/src/routes/ResetPassword/components/ResetPasswordView.jsx index aacbd09..3fe1671 100644 --- a/src/routes/ResetPassword/components/ResetPasswordView.jsx +++ b/src/routes/ResetPassword/components/ResetPasswordView.jsx @@ -6,6 +6,8 @@ import FormField from '../../../components/FormField'; import Button from '../../../components/Button'; import {reduxForm} from 'redux-form'; import {sendRequest} from '../modules/ResetPassword'; +import {browserHistory} from 'react-router'; +import {toastr} from 'react-redux-toastr'; class ResetPasswordView extends Component { @@ -14,7 +16,13 @@ class ResetPasswordView extends Component { * This is triggered by handleSubmit */ onSubmit(data) { - return sendRequest(data); + sendRequest(data).then(() => { + toastr.success('', 'Password reset successfuly, kindly login again'); + browserHistory.push('/'); + }).catch((reason) => { + const message = reason.response.body.error || 'something went wrong, please try again'; + toastr.error(message); + }); } render() { @@ -32,7 +40,7 @@ class ResetPasswordView extends Component {
- +
diff --git a/src/routes/ResetPassword/containers/ResetPasswordContainer.js b/src/routes/ResetPassword/containers/ResetPasswordContainer.js index 6abbe02..f6309c7 100644 --- a/src/routes/ResetPassword/containers/ResetPasswordContainer.js +++ b/src/routes/ResetPassword/containers/ResetPasswordContainer.js @@ -1,6 +1,5 @@ import {asyncConnect} from 'redux-connect'; import {actions} from '../modules/ResetPassword'; -import {browserHistory} from 'react-router'; import ResetPasswordView from '../components/ResetPasswordView'; @@ -8,10 +7,6 @@ const resolve = [{ promise: () => Promise.resolve(), }]; -const handleSuccess = () => { - browserHistory.push('/'); -}; - -const mapState = (state) => ({...state.resetPassword, onSubmitSuccess: handleSuccess}); +const mapState = (state) => state.resetPassword; export default asyncConnect(resolve, mapState, actions)(ResetPasswordView); diff --git a/src/routes/ResetPassword/index.js b/src/routes/ResetPassword/index.js index ff64d00..eeb7dbb 100644 --- a/src/routes/ResetPassword/index.js +++ b/src/routes/ResetPassword/index.js @@ -11,7 +11,7 @@ export default (store) => ({ injectReducer(store, {key: 'resetPassword', reducer}); if (!nextState.location.query.token) { - cb(new Error('Invalid route invokation')); + cb(new Error('Invalid route invocation')); } else { cb(null, Dashboard); } diff --git a/src/services/APIService.js b/src/services/APIService.js index 391d037..43c7f2d 100644 --- a/src/services/APIService.js +++ b/src/services/APIService.js @@ -3,7 +3,7 @@ import _ from 'lodash'; import superagent from 'superagent'; import superagentPromise from 'superagent-promise'; -import config from '../../config/default'; +import config from '../config'; // DEMO: emulate API requests with dummy data for demo purposes diff --git a/src/services/AuthService.js b/src/services/AuthService.js index b28af64..fbb2650 100644 --- a/src/services/AuthService.js +++ b/src/services/AuthService.js @@ -1,7 +1,7 @@ /** * Copyright (c) 2016 Topcoder Inc, All rights reserved. */ -/* eslint no-console: 0 */ + /** * auth0 Authentication service for the app. * @@ -10,11 +10,13 @@ */ import Auth0 from 'auth0-js'; -import config from '../../config/default'; +import config from '../config'; import UserApi from '../api/User'; +import _ from 'lodash'; const userApi = new UserApi(config.API_BASE_PATH); +const idTokenKey = 'id_token'; class AuthService { @@ -68,9 +70,7 @@ class AuthService { _self.removeToken(); throw error; } else { - userApi.registerSocialUser(profile.name, profile.email, _self.getToken()).then((loginResult) => { - console.log('user registered successfully', loginResult); - }).catch((reason) => { + userApi.registerSocialUser(profile.name, profile.email, _self.getToken()).then(_.noop).catch((reason) => { // remove the id token _self.removeToken(); throw reason; @@ -95,7 +95,7 @@ class AuthService { */ setToken(idToken) { // Saves user token to localStorage - localStorage.setItem('id_token', idToken); + localStorage.setItem(idTokenKey, idToken); } /** @@ -103,7 +103,7 @@ class AuthService { */ getToken() { // Retrieves the user token from localStorage - return localStorage.getItem('id_token'); + return localStorage.getItem(idTokenKey); } /** @@ -111,7 +111,7 @@ class AuthService { */ removeToken() { // Clear user token and profile data from localStorage - localStorage.removeItem('id_token'); + localStorage.removeItem(idTokenKey); } /** diff --git a/src/store/modules/global.js b/src/store/modules/global.js index 6768e8f..4373f22 100644 --- a/src/store/modules/global.js +++ b/src/store/modules/global.js @@ -1,7 +1,7 @@ import {handleActions, createAction} from 'redux-actions'; import {browserHistory} from 'react-router'; import UserApi from 'api/User.js'; -import config from '../../../config/default'; +import config from '../../config'; const userApi = new UserApi(config.API_BASE_PATH); From 00facdc7d93e989d3719910449e9186438c101f7 Mon Sep 17 00:00:00 2001 From: riteshsangwan Date: Wed, 28 Dec 2016 21:26:52 +0530 Subject: [PATCH 8/9] sync config with env sample --- envSample | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/envSample b/envSample index 4a42b68..003a7ca 100644 --- a/envSample +++ b/envSample @@ -1,5 +1,5 @@ -REACT_APP_API_BASE_PATH=https://kb-dsp-server.herokuapp.com -REACT_APP_SOCKET_URL=https://kb-dsp-server.herokuapp.com -REACT_APP_AUTH0_CLIEND_ID=3CGKzjS2nVSqHxHHE64RhvvKY6e0TYpK -REACT_APP_AUTH0_DOMAIN=dronetest.auth0.com -REACT_APP_GOOGLE_API_KEY=AIzaSyCR3jfBdv9prCBYBOf-fPUDhjPP4K05YjE +GOOGLE_API_KEY=AIzaSyCrL-O319wNJK8kk8J_JAYsWgu6yo5YsDI +API_BASE_PATH=http://localhost:3500 +REACT_APP_AUTH0_CLIENT_ID=h7p6V93Shau3SSvqGrl6V4xrATlkrVGm +REACT_APP_AUTH0_CLIENT_DOMAIN=spanhawk.auth0.com +REACT_APP_SOCKET_URL=http://localhost:3500 \ No newline at end of file From 232a26bb4360f579d8382136e2dd363be0a66a7d Mon Sep 17 00:00:00 2001 From: riteshsangwan Date: Wed, 28 Dec 2016 21:32:01 +0530 Subject: [PATCH 9/9] remove twice handleLoggedIn call --- src/routes/Home/components/LoginModal/LoginModal.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/routes/Home/components/LoginModal/LoginModal.jsx b/src/routes/Home/components/LoginModal/LoginModal.jsx index e912c20..2ed19ff 100644 --- a/src/routes/Home/components/LoginModal/LoginModal.jsx +++ b/src/routes/Home/components/LoginModal/LoginModal.jsx @@ -77,7 +77,6 @@ class LogInModal extends React.Component { } handleLogin(handleLoggedIn, loggedUser) { - handleLoggedIn(); const _self = this; setTimeout(() => { handleLoggedIn(); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy