191 lines
6.4 KiB
JavaScript
Executable file
191 lines
6.4 KiB
JavaScript
Executable file
import React from 'react';
|
|
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
|
import {Link} from 'react-router';
|
|
import classNames from 'classnames';
|
|
import Flux from '../lib/flux';
|
|
import BaseAuthForm from './BaseAuthForm';
|
|
import ConfirmModal from './ConfirmModal';
|
|
import SaveButton from './common/SaveButton';
|
|
import AuthenticationActionCreators from '../actions/AuthenticationActionCreators';
|
|
import ModalActionCreators from '../actions/ModalActionCreators';
|
|
import AuthenticationStore from '../stores/AuthenticationStore';
|
|
import querystring from 'querystring';
|
|
import i18n from '../i18n';
|
|
import RouterUtils from '../utils/RouterUtils';
|
|
import {ActionTypes, ChannelTypes, Endpoints, Routes, LoginStates} from '../Constants';
|
|
import {isSafeRedirect} from '../utils/RedirectUtils';
|
|
import HTTPUtils from '../utils/HTTPUtils';
|
|
import Dispatcher from '../Dispatcher';
|
|
|
|
const CODE_REF = 'code';
|
|
|
|
const Login = React.createClass({
|
|
mixins: [PureRenderMixin, Flux.StoreListenerMixin(AuthenticationStore)],
|
|
|
|
getStateFromStores() {
|
|
return {
|
|
loginStatus: AuthenticationStore.getLoginStatus(),
|
|
mfaTicket: AuthenticationStore.getMFATicket(),
|
|
errors: AuthenticationStore.getErrors(),
|
|
authenticated: AuthenticationStore.isAuthenticated(),
|
|
};
|
|
},
|
|
|
|
componentDidMount() {
|
|
AuthenticationActionCreators.fingerprint('Login');
|
|
this.loginOrSSO();
|
|
},
|
|
|
|
componentDidUpdate() {
|
|
this.loginOrSSO();
|
|
},
|
|
|
|
loginOrSSO() {
|
|
if (this.state.authenticated) {
|
|
// We should double-check that the user's token is *valid* before forwarding them to their destination
|
|
HTTPUtils.get({
|
|
url: Endpoints.ME,
|
|
}).then(
|
|
() => {
|
|
const {query} = this.props.location;
|
|
if (query['redirect_to'] && isSafeRedirect(query['redirect_to'])) {
|
|
window.location = query['redirect_to'];
|
|
} else if (query['service'] == null) {
|
|
RouterUtils.transitionTo(Routes.ME);
|
|
} else {
|
|
const prefix = location.protocol + process.env.API_ENDPOINT + Endpoints.SSO;
|
|
window.location = `${prefix}?token=${AuthenticationStore.getToken()}&${querystring.stringify(query)}`;
|
|
}
|
|
},
|
|
() => {
|
|
Dispatcher.dispatch({type: ActionTypes.LOGOUT});
|
|
}
|
|
);
|
|
}
|
|
},
|
|
|
|
handleSubmit(e) {
|
|
e.preventDefault();
|
|
AuthenticationActionCreators.login(this.refs.email.value, this.refs.password.value);
|
|
},
|
|
|
|
handleTokenSubmit(e) {
|
|
e.preventDefault();
|
|
this.setState({loggingInViaMFA: true});
|
|
AuthenticationActionCreators.loginMFA(this.refs[CODE_REF].value, this.state.mfaTicket);
|
|
},
|
|
|
|
handleForgotPassword() {
|
|
const email = this.refs.email.value;
|
|
AuthenticationActionCreators.forgotPassword(email, () => {
|
|
ModalActionCreators.push(props => {
|
|
return (
|
|
<ConfirmModal
|
|
header={i18n.Messages.EMAIL_VERIFICATION_INSTRUCTIONS_HEADER}
|
|
confirmText={i18n.Messages.OKAY}
|
|
red={false}
|
|
{...props}>
|
|
<p>{i18n.Messages.EMAIL_VERIFICATION_INSTRUCTIONS_BODY.format({email})}</p>
|
|
</ConfirmModal>
|
|
);
|
|
});
|
|
});
|
|
},
|
|
|
|
render() {
|
|
const {errors, loginStatus} = this.state;
|
|
|
|
const hasError = field => errors[field] != null;
|
|
const renderError = field => {
|
|
if (hasError(field)) {
|
|
return <span className="error">({errors[field]})</span>;
|
|
} else {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
let header;
|
|
let registerLink;
|
|
const invite = this.props.invite;
|
|
if (invite != null) {
|
|
const channelName = invite.channel.type === ChannelTypes.GUILD_TEXT
|
|
? `#${invite.channel.name}`
|
|
: invite.channel.name;
|
|
const serverName = invite.guild.name;
|
|
header = (
|
|
<h1>
|
|
{i18n.Messages.LOGIN_TITLE}
|
|
<p>{i18n.Messages.INSTANT_INVITE_RESOLVED_BODY.format({channelName, serverName})}</p>
|
|
</h1>
|
|
);
|
|
registerLink = {
|
|
pathname: Routes.INVITE(invite.code),
|
|
query: {
|
|
mode: 'register',
|
|
},
|
|
};
|
|
} else {
|
|
header = <h1>{i18n.Messages.LOGIN_TITLE}</h1>;
|
|
registerLink = {
|
|
pathname: Routes.REGISTER,
|
|
query: this.props.location.query,
|
|
};
|
|
}
|
|
|
|
if (this.state.mfaTicket || this.state.loggingInViaMFA) {
|
|
return (
|
|
<BaseAuthForm onSubmit={this.handleTokenSubmit} invite={invite}>
|
|
{header}
|
|
<div className={classNames({'control-group': true, error: hasError(CODE_REF)})}>
|
|
<label htmlFor="mfa-code">{i18n.Messages.TWO_FA_ENTER_TOKEN_LABEL} {renderError(CODE_REF)}</label>
|
|
<input
|
|
id="mfa-code"
|
|
key={CODE_REF}
|
|
ref={CODE_REF}
|
|
type="text"
|
|
autoComplete="off"
|
|
spellCheck="false"
|
|
maxLength={10}
|
|
autoFocus
|
|
defaultValue=""
|
|
/>
|
|
<p className="token-tip">{i18n.Messages.TWO_FA_ENTER_TOKEN_BODY}</p>
|
|
</div>
|
|
<SaveButton className="btn btn-primary" disabled={loginStatus === LoginStates.LOGGING_IN_MFA}>
|
|
{i18n.Messages.LOGIN}
|
|
</SaveButton>
|
|
</BaseAuthForm>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<BaseAuthForm onSubmit={this.handleSubmit} invite={invite}>
|
|
{header}
|
|
<div className={classNames({'control-group': true, error: hasError('email')})}>
|
|
<label htmlFor="register-email">{i18n.Messages.FORM_LABEL_EMAIL} {renderError('email')}</label>
|
|
<input id="register-email" ref="email" type="email" autoComplete="off" spellCheck="false" autoFocus />
|
|
</div>
|
|
<div className={classNames({'control-group': true, error: hasError('password')})}>
|
|
<label htmlFor="register-password">{i18n.Messages.FORM_LABEL_PASSWORD} {renderError('password')}</label>
|
|
<input id="register-password" ref="password" type="password" autoComplete="off" spellCheck="false" />
|
|
<button className="btn-forgot-password" type="button" onClick={this.handleForgotPassword}>
|
|
{i18n.Messages.FORGOT_PASSWORD}
|
|
</button>
|
|
</div>
|
|
<SaveButton className="btn btn-primary" disabled={loginStatus === LoginStates.LOGGING_IN}>
|
|
{i18n.Messages.LOGIN}
|
|
</SaveButton>
|
|
<footer>
|
|
{i18n.Messages.NEED_ACCOUNT} <Link to={registerLink}>{i18n.Messages.REGISTER}</Link>
|
|
</footer>
|
|
</BaseAuthForm>
|
|
);
|
|
},
|
|
});
|
|
|
|
export default Login;
|
|
|
|
|
|
|
|
// WEBPACK FOOTER //
|
|
// ./discord_app/components/Login.js
|