diff --git a/client/coral-admin/src/actions/auth.js b/client/coral-admin/src/actions/auth.js index 389e8b4d2..3e6d81ae1 100644 --- a/client/coral-admin/src/actions/auth.js +++ b/client/coral-admin/src/actions/auth.js @@ -2,15 +2,24 @@ import * as actions from '../constants/auth'; import coralApi from 'coral-framework/helpers/response'; // Log In. -export const handleLogin = (email, password) => dispatch => { +export const handleLogin = (email, password, recaptchaResponse) => dispatch => { dispatch({type: actions.LOGIN_REQUEST}); - return coralApi('/auth/local', {method: 'POST', body: {email, password}}) + const params = {method: 'POST', body: {email, password}}; + if (recaptchaResponse) { + params.headers = {'X-Recaptcha-Response': recaptchaResponse}; + } + return coralApi('/auth/local', params) .then(result => { const isAdmin = !!result.user.roles.filter(i => i === 'ADMIN').length; dispatch(checkLoginSuccess(result.user, isAdmin)); }) .catch(error => { - dispatch({type: actions.LOGIN_FAILURE, message: error.translation_key}); + + if (error.translation_key === 'LOGIN_MAXIMUM_EXCEEDED') { + dispatch({type: actions.LOGIN_MAXIMUM_EXCEEDED, message: error.translation_key}); + } else { + dispatch({type: actions.LOGIN_FAILURE, message: error.translation_key}); + } }); }; diff --git a/client/coral-admin/src/components/AdminLogin.js b/client/coral-admin/src/components/AdminLogin.js index 7c7d4b00f..11e840eb7 100644 --- a/client/coral-admin/src/components/AdminLogin.js +++ b/client/coral-admin/src/components/AdminLogin.js @@ -4,6 +4,7 @@ import styles from './NotFound.css'; import {Button, TextField, Alert, Success} from 'coral-ui'; import I18n from 'coral-framework/modules/i18n/i18n'; import translations from '../translations'; +import Recaptcha from 'react-recaptcha'; const lang = new I18n(translations); class AdminLogin extends React.Component { @@ -18,13 +19,22 @@ class AdminLogin extends React.Component { this.props.handleLogin(this.state.email, this.state.password); } + onRecaptchaLoad = () => { + + // do something? + } + + onRecaptchaVerify = (recaptchaResponse) => { + this.props.handleLogin(this.state.email, this.state.password, recaptchaResponse); + } + handleRequestPassword = e => { e.preventDefault(); this.props.requestPasswordReset(this.state.email); } render () { - const {errorMessage} = this.props; + const {errorMessage, loginMaxExceeded} = this.props; const signInForm = (
{errorMessage && {lang.t(`errors.${errorMessage}`)}} @@ -49,6 +59,15 @@ class AdminLogin extends React.Component { this.setState({requestPassword: true}); }}>Request a new one.

+ { + loginMaxExceeded && + + } ); const requestPasswordForm = ( @@ -84,6 +103,7 @@ class AdminLogin extends React.Component { } AdminLogin.propTypes = { + loginMaxExceeded: PropTypes.bool.isRequired, handleLogin: PropTypes.func.isRequired, passwordRequestSuccess: PropTypes.string, loginError: PropTypes.string diff --git a/client/coral-admin/src/constants/auth.js b/client/coral-admin/src/constants/auth.js index 3c47bfc45..93cd61544 100644 --- a/client/coral-admin/src/constants/auth.js +++ b/client/coral-admin/src/constants/auth.js @@ -11,6 +11,7 @@ export const LOGOUT_FAILURE = 'LOGOUT_FAILURE'; export const LOGIN_REQUEST = 'LOGIN_REQUEST'; export const LOGIN_SUCCESS = 'LOGIN_SUCCESS'; export const LOGIN_FAILURE = 'LOGIN_FAILURE'; +export const LOGIN_MAXIMUM_EXCEEDED = 'LOGIN_MAXIMUM_EXCEEDED'; export const FETCH_FORGOT_PASSWORD_REQUEST = 'FETCH_FORGOT_PASSWORD_REQUEST'; export const FETCH_FORGOT_PASSWORD_SUCCESS = 'FETCH_FORGOT_PASSWORD_SUCCESS'; diff --git a/client/coral-admin/src/containers/LayoutContainer.js b/client/coral-admin/src/containers/LayoutContainer.js index d122cd23a..d2493a548 100644 --- a/client/coral-admin/src/containers/LayoutContainer.js +++ b/client/coral-admin/src/containers/LayoutContainer.js @@ -16,12 +16,14 @@ class LayoutContainer extends Component { loggedIn, loadingUser, loginError, + loginMaxExceeded, passwordRequestSuccess } = this.props.auth; const {handleLogout} = this.props; if (loadingUser) { return ; } if (!isAdmin) { return ({ const mapDispatchToProps = dispatch => ({ checkLogin: () => dispatch(checkLogin()), - handleLogin: (username, password) => dispatch(handleLogin(username, password)), + handleLogin: (username, password, recaptchaResponse) => dispatch(handleLogin(username, password, recaptchaResponse)), requestPasswordReset: email => dispatch(requestPasswordReset(email)), handleLogout: () => dispatch(logout()) }); diff --git a/client/coral-admin/src/reducers/auth.js b/client/coral-admin/src/reducers/auth.js index b52efdb85..a7054ddfa 100644 --- a/client/coral-admin/src/reducers/auth.js +++ b/client/coral-admin/src/reducers/auth.js @@ -6,6 +6,7 @@ const initialState = Map({ user: null, isAdmin: false, loginError: null, + loginMaxExceeded: false, passwordRequestSuccess: null }); @@ -29,12 +30,18 @@ export default function auth (state = initialState, action) { return initialState; case actions.LOGIN_REQUEST: return state.set('loginError', null); + case actions.LOGIN_SUCCESS: + return state.set('loginMaxExceeded', false).set('loginError', null); case actions.LOGIN_FAILURE: return state.set('loginError', action.message); case actions.FETCH_FORGOT_PASSWORD_REQUEST: return state.set('passwordRequestSuccess', null); case actions.FETCH_FORGOT_PASSWORD_SUCCESS: return state.set('passwordRequestSuccess', 'If you have a registered account, a password reset link was sent to that email.'); + case actions.LOGIN_MAXIMUM_EXCEEDED: + return state + .set('loginMaxExceeded', true) + .set('loginError', action.message); default : return state; } diff --git a/package.json b/package.json index 7da9305f8..36899a04b 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "passport-facebook": "^2.1.1", "passport-local": "^1.0.0", "react-apollo": "^0.10.0", + "react-recaptcha": "^2.2.6", "redis": "^2.6.5", "uuid": "^2.0.3" }, diff --git a/views/admin.ejs b/views/admin.ejs index dafd5e3cc..d4d635dcc 100644 --- a/views/admin.ejs +++ b/views/admin.ejs @@ -85,6 +85,7 @@
+ diff --git a/webpack.config.js b/webpack.config.js index 432bb6155..e7652a67d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -118,6 +118,9 @@ module.exports = { 'process.env': { 'VERSION': `"${require('./package.json').version}"` } + }), + new webpack.EnvironmentPlugin({ + TALK_RECAPTCHA_PUBLIC: JSON.stringify(process.env.TALK_RECAPTCHA_PUBLIC) }) ], resolve: { diff --git a/yarn.lock b/yarn.lock index a2308f16d..b5f96464e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6452,6 +6452,10 @@ react-onclickoutside@^5.7.1: version "5.8.4" resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-5.8.4.tgz#a2c673a8d1b104a550e565574b95419feb12cc3f" +react-recaptcha@^2.2.6: + version "2.2.6" + resolved "https://registry.yarnpkg.com/react-recaptcha/-/react-recaptcha-2.2.6.tgz#bb44c1948a39b37d5a41920c73db833e5d8524f9" + react-redux@^4.4.5: version "4.4.6" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-4.4.6.tgz#4b9d32985307a11096a2dd61561980044fcc6209"