Reimplement recaptcha

This commit is contained in:
Chi Vinh Le
2018-02-07 22:10:05 +01:00
parent bad0363628
commit aaad7aa86f
11 changed files with 100 additions and 47 deletions
-7
View File
@@ -1,7 +0,0 @@
export const CONFIG_UPDATED = 'CONFIG_UPDATED';
export const fetchConfig = () => dispatch => {
let json = document.getElementById('data');
let data = JSON.parse(json.textContent);
dispatch({ type: CONFIG_UPDATED, data });
};
+23 -4
View File
@@ -2,11 +2,10 @@ import React from 'react';
import PropTypes from 'prop-types';
import styles from './SignIn.css';
import { Button, TextField, Alert } from 'coral-ui';
import Recaptcha from 'coral-framework/components/Recaptcha';
class SignIn extends React.Component {
constructor(props) {
super(props);
}
recaptcha = null;
handleForgotPasswordLink = e => {
e.preventDefault();
@@ -18,10 +17,23 @@ class SignIn extends React.Component {
handleSubmit = e => {
e.preventDefault();
this.props.onSubmit();
// Reset recaptcha because each response can only
// be used once.
if (this.recaptcha) {
this.recaptcha.reset();
}
};
handleRecaptchaRef = ref => {
this.recaptcha = ref;
setTimeout(() => {
console.log(ref);
}, 1000)
};
render() {
const { email, password, errorMessage } = this.props;
const { email, password, errorMessage, requireRecaptcha } = this.props;
return (
<form className="talk-admin-login-sign-in" onSubmit={this.handleSubmit}>
{errorMessage && <Alert>{errorMessage}</Alert>}
@@ -57,6 +69,12 @@ class SignIn extends React.Component {
Request a new one.
</a>
</p>
{requireRecaptcha && (
<Recaptcha
ref={this.handleRecaptchaRef}
onVerify={this.props.onRecaptchaVerify}
/>
)}
</form>
);
}
@@ -68,6 +86,7 @@ SignIn.propTypes = {
onEmailChange: PropTypes.func.isRequired,
onPasswordChange: PropTypes.func.isRequired,
onForgotPasswordLink: PropTypes.func.isRequired,
onRecaptchaVerify: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
errorMessage: PropTypes.string.isRequired,
requireRecaptcha: PropTypes.bool.isRequired,
@@ -2,7 +2,6 @@ import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import Layout from '../components/Layout';
import { fetchConfig } from '../actions/config';
import Login from '../containers/Login';
import { FullLoading } from '../components/FullLoading';
import BanUserDialog from './BanUserDialog';
@@ -14,12 +13,6 @@ import UserDetail from 'coral-admin/src/containers/UserDetail';
import PropTypes from 'prop-types';
class LayoutContainer extends React.Component {
componentWillMount() {
const { fetchConfig } = this.props;
fetchConfig();
}
render() {
const {
currentUser,
@@ -72,8 +65,6 @@ LayoutContainer.propTypes = {
checkedInitialLogin: PropTypes.bool,
logout: PropTypes.func,
toggleShortcutModal: PropTypes.func,
TALK_RECAPTCHA_PUBLIC: PropTypes.string,
fetchConfig: PropTypes.func,
};
const mapStateToProps = state => ({
@@ -84,7 +75,6 @@ const mapStateToProps = state => ({
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
fetchConfig,
toggleShortcutModal,
logout,
},
+11 -1
View File
@@ -8,10 +8,15 @@ class SignInContainer extends Component {
state = {
email: '',
password: '',
recaptchaResponse: '',
};
handleSubmit = () => {
this.props.signIn(this.state.email, this.state.password);
this.props.signIn(
this.state.email,
this.state.password,
this.state.recaptchaResponse
);
};
handleEmailChange = email => {
@@ -22,6 +27,10 @@ class SignInContainer extends Component {
this.setState({ password });
};
handleRecaptchaVerify = recaptchaResponse => {
this.setState({ recaptchaResponse });
};
render() {
return (
<SignIn
@@ -32,6 +41,7 @@ class SignInContainer extends Component {
password={this.state.password}
errorMessage={this.props.errorMessage}
onForgotPasswordLink={this.props.onForgotPasswordLink}
onRecaptchaVerify={this.handleRecaptchaVerify}
requireRecaptcha={this.props.requireRecaptcha}
/>
);
-17
View File
@@ -1,17 +0,0 @@
import * as actions from '../actions/config';
const initialState = {
data: {},
};
export default function config(state = initialState, action) {
switch (action.type) {
case actions.CONFIG_UPDATED:
return {
...state,
data: action.data,
};
default:
return state;
}
}
-2
View File
@@ -4,7 +4,6 @@ import configure from './configure';
import community from './community';
import moderation from './moderation';
import install from './install';
import config from './config';
import banUserDialog from './banUserDialog';
import suspendUserDialog from './suspendUserDialog';
import userDetail from './userDetail';
@@ -20,6 +19,5 @@ export default {
community,
moderation,
install,
config,
ui,
};
@@ -0,0 +1,52 @@
import React from 'react';
import PropTypes from 'prop-types';
import ReactRecaptcha from 'react-recaptcha';
class Recaptcha extends React.Component {
static contextTypes = {
store: PropTypes.object,
};
ref = null;
handleRef = ref => {
this.ref = ref;
};
reset = () => this.ref.reset();
getSiteKey() {
// This should be fine because it's static and will never change.
// Prefer this to connect HOC because wie expose the instance method
// `reset`
return this.context.store.getState().static.TALK_RECAPTCHA_PUBLIC;
}
render() {
return (
<ReactRecaptcha
ref={this.handleRef}
sitekey={this.getSiteKey()}
render={this.props.render}
theme={this.props.theme}
onloadCallback={this.props.onLoad}
verifyCallback={this.props.onVerify}
/>
);
}
}
Recaptcha.defaultProps = {
render: 'explicit',
theme: 'dark',
};
Recaptcha.propTypes = {
onLoad: PropTypes.func,
onVerify: PropTypes.func.isRequired,
theme: PropTypes.string,
render: PropTypes.string,
sitekey: PropTypes.string.isRequired,
};
export default Recaptcha;
+2 -1
View File
@@ -5,6 +5,7 @@ import { connect } from 'react-redux';
import kebabCase from 'lodash/kebabCase';
import PropTypes from 'prop-types';
import isEqual from 'lodash/isEqual';
import get from 'lodash/get';
import { getShallowChanges } from 'coral-framework/utils';
const emptyConfig = {};
@@ -68,7 +69,7 @@ class Slot extends React.Component {
} = this.props;
const { plugins } = this.context;
let children = this.getChildren();
const pluginConfig = reduxState.config.pluginConfig || emptyConfig;
const pluginConfig = get(reduxState, 'config.pluginConfig') || emptyConfig;
if (children.length === 0 && DefaultComponent) {
const props = plugins.getSlotComponentProps(
DefaultComponent,
+8 -2
View File
@@ -18,6 +18,7 @@ export default hoistStatics(WrappedComponent => {
state = {
error: null,
loading: false,
requireRecaptcha: false,
};
signIn = (email, password, recaptchaResponse) => {
@@ -45,7 +46,12 @@ export default hoistStatics(WrappedComponent => {
if (!error.status || error.status !== 401) {
console.error(error);
}
this.setState({ loading: false, error });
const changeSet = { loading: false, error };
if (error.translation_key === 'LOGIN_MAXIMUM_EXCEEDED') {
changeSet.requireRecaptcha = !!this.context.store.getState().static
.TALK_RECAPTCHA_PUBLIC;
}
this.setState(changeSet);
});
};
@@ -65,7 +71,7 @@ export default hoistStatics(WrappedComponent => {
signIn={this.signIn}
loading={this.state.loading}
errorMessage={this.getErrorMessage()}
requireRecaptcha={false}
requireRecaptcha={this.state.requireRecaptcha}
/>
);
}
+1 -1
View File
@@ -35,7 +35,7 @@ import { setStaticConfiguration } from '../actions/static';
const getAuthToken = (store, storage) => {
let state = store.getState();
if (state.config.auth_token) {
if (state.config && state.config.auth_token) {
// if an auth_token exists in config, use it.
return state.config.auth_token;
} else if (!bowser.safari && !bowser.ios && storage) {
+3 -2
View File
@@ -6,6 +6,7 @@ import flattenDeep from 'lodash/flattenDeep';
import isEmpty from 'lodash/isEmpty';
import flatten from 'lodash/flatten';
import mapValues from 'lodash/mapValues';
import get from 'lodash/get';
import { getDisplayName } from 'coral-framework/helpers/hoc';
import camelize from '../helpers/camelize';
@@ -83,7 +84,7 @@ class PluginsService {
* query datas are only passed to the component if it is defined in `component.fragments`.
*/
getSlotComponentProps(component, reduxState, props, queryData) {
const pluginConfig = reduxState.config.plugin_config || emptyConfig;
const pluginConfig = get(reduxState, 'config.plugin_config') || emptyConfig;
return {
...props,
config: pluginConfig,
@@ -97,7 +98,7 @@ class PluginsService {
* Returns React Elements for given slot.
*/
getSlotElements(slot, reduxState, props = {}, queryData = {}) {
const pluginConfig = reduxState.config.plugin_config || emptyConfig;
const pluginConfig = get(reduxState, 'config.plugin_config') || emptyConfig;
const isDisabled = component => {
if (