@luminati-io/luminati-proxy
Version:
A configurable local proxy for luminati.io
410 lines (396 loc) • 16.1 kB
JavaScript
// LICENSE_CODE ZON ISC
; /*jslint react:true, es6:true*/
import React from 'react';
import {withRouter} from 'react-router-dom';
import Pure_component from '/www/util/pub/pure_component.js';
import {Typeahead} from 'react-bootstrap-typeahead';
import ajax from '../../util/ajax.js';
import etask from '../../util/etask.js';
import setdb from '../../util/setdb.js';
import zurl from '../../util/url.js';
import {Loader, Logo, with_www_api} from './common.js';
import {T} from './common/i18n.js';
import './css/login.less';
const token_err_msg = {
'bad token': 'Invalid token',
'token expired': 'Expired token. A new token has been sent',
default: 'Something went wrong'
};
const Login = withRouter(class Login extends Pure_component {
state = {password: '', username: '', loading: false, two_step: false,
two_step_verified: false, customer_selected: false,
sending_email: false};
componentDidMount(){
this.setdb_on('head.argv', argv=>{
if (argv)
this.setState({argv});
});
this.setdb_on('head.ver_node', ver_node=>this.setState({ver_node}));
const url_o = zurl.parse(document.location.href);
const qs_o = zurl.qs_parse((url_o.search||'').substr(1));
if (qs_o.t)
{
this.token = qs_o.t.replace(/\s+/g, '+');
this.save_user();
}
}
update_password = ({target: {value}})=>this.setState({password: value});
update_username = ({target: {value}})=>this.setState({username: value});
select_customer = customer=>this.setState({customer});
select_final_customer = ()=>{
if (this.state.customer)
this.setState({customer_selected: true});
this.save_user();
};
save_user = ()=>{
const creds = {};
if (this.token)
creds.token = this.token;
else
{
creds.username = this.state.username;
creds.password = this.state.password;
}
if (this.state.customer)
creds.customer = this.state.customer;
const _this = this;
this.etask(function*(){
this.on('uncaught', e=>{
const update = {loading: false};
if (e.message=='Unauthorized')
update.error_message = 'Unauthorized';
else
update.error_message = 'Something went wrong';
_this.setState(update);
});
this.on('finally', ()=>_this.setState({loading: false}));
_this.setState({loading: true});
const res = yield ajax.json({url: '/api/creds_user',
method: 'POST', data: creds, timeout: 60000});
if (res.error)
{
_this.setState({
customer: null,
user_customers: null,
customer_selected: false,
error_message: res.error.message||'Something went wrong',
});
}
else if (res.customers || res.ask_two_step)
{
_this.setState({
user_customers: res.customers,
error_message: '',
two_step: res.ask_two_step,
});
}
else
yield _this.get_in();
});
};
verify_two_step = data=>{
const _this = this;
this.etask(function*(){
_this.setState({loading: true});
const res = yield ajax.json({url: '/api/verify_two_step',
method: 'POST', data, no_throw: true});
if (res && res.error)
{
_this.setState({loading: false, error_message:
token_err_msg[res.message]||token_err_msg.default});
}
else
_this.setState({two_step_verified: true}, _this.save_user);
});
};
send_two_step_email = ()=>{
const _this = this;
this.etask(function*(){
this.on('finally', ()=>_this.setState({sending_email: false}));
_this.setState({sending_email: true});
const res = yield ajax.json({url: '/api/send_two_step_email',
method: 'POST', no_throw: true});
if (res && res.error)
{
_this.setState({error_message:
res.error.message||'Something went wrong'});
}
});
};
get_in = ()=>{
const _this = this;
return this.etask(function*(){
this.on('uncaught', e=>{
_this.setState({error_message: 'Cannot log in: '+e.message});
});
const ets = [];
ets.push(etask(function*_get_settings(){
const settings = yield ajax.json({url: '/api/settings'});
setdb.set('head.settings', settings);
}));
ets.push(etask(function*_get_consts(){
const consts = yield ajax.json({url: '/api/consts'});
setdb.set('head.consts', consts);
}));
const curr_locations = setdb.get('head.locations');
if (!curr_locations || !curr_locations.shared_countries)
{
ets.push(etask(function*_get_locations(){
const locations = yield ajax.json(
{url: '/api/all_locations'});
locations.countries_by_code = locations.countries.reduce(
(acc, e)=>({...acc, [e.country_id]: e.country_name}), {});
setdb.set('head.locations', locations);
}));
}
ets.push(etask(function*_get_zones(){
const zones = yield ajax.json({url: '/api/zones'});
setdb.set('head.zones', zones);
}));
ets.push(etask(function*_get_proxies_running(){
const proxies = yield ajax.json({url: '/api/proxies_running'});
setdb.set('head.proxies_running', proxies);
}));
yield etask.all(ets);
_this.props.history.push('/overview');
});
};
render(){
return <div className="lum_login">
<Logo/>
<Messages error_message={this.state.error_message}
argv={this.state.argv}
ver_node={this.state.ver_node}/>
<Loader show={this.state.loading}/>
<Header/>
<Form save_user={this.save_user}
user_customers={this.state.user_customers}
customer_selected={this.state.customer_selected}
two_step={this.state.two_step}
password={this.state.password}
username={this.state.username}
loading={this.state.loading}
sending_email={this.state.sending_email}
update_password={this.update_password}
update_username={this.update_username}
select_customer={this.select_customer}
select_final_customer={this.select_final_customer}
send_two_step_email={this.send_two_step_email}
verify_two_step={this.verify_two_step}/>
</div>;
}
});
const parse_arguments = argv=>
argv.replace(/(--password )(.+?)( --|$)/, '$1|||$2|||$3').split('|||');
const Messages = ({error_message, argv, ver_node})=>
<div>
{argv &&
<div className="warning">
<div className="warning_icon"/>
The application is running with the following arguments:
{parse_arguments(argv).map(a=><strong key={a}>{a}</strong>)}
</div>
}
{error_message &&
<div className="warning error settings-alert">
<div dangerouslySetInnerHTML={{__html: error_message}}/>
</div>
}
<Node_message ver_node={ver_node}/>
</div>;
const Node_message = ({ver_node})=>{
if (!ver_node || ver_node.is_electron || ver_node.satisfied)
return null;
return <div className="warning settings-alert">
<div className="warning_icon"/>
<div>
<div>
<span>The recommended version of node.js is </span>
<strong>{ver_node.recommended}</strong>.
<span>You are using version </span>
<strong>{ver_node.current && ver_node.current.raw}</strong>.
</div>
<div>
Please upgrade your node using nvm, nave or visit
<a href="https://nodejs.org">node.js</a>and download a newer
version.
</div>
<div>
After node upgrade you should uninstall and then reinstall this
tool using:
</div>
<pre className="top-margin">
npm uninstall -g @luminati-io/luminati-proxy</pre>
<pre className="top-margin">
npm install -g @luminati-io/luminati-proxy</pre>
</div>
</div>;
};
const Header = ()=>
<div className="login_header">
<h3><T>Login with your Luminati account</T></h3>
</div>;
const Form = props=>{
const google_login_url = 'https://accounts.google.com/o/oauth2/v2/auth?'
+'client_id=943425271003-8ibddns3o1ftp59t2su8c3psocph9v1d.apps.'
+'googleusercontent.com&response_type=code&redirect_uri='
+'https%3A%2F%2Fluminati.io%2Fcp%2Flum_local_google&scope=https%3A%2F%2F'
+'www.googleapis.com%2Fauth%2Fuserinfo.email&prompt=select_account';
const google_click = e=>{
const l = window.location;
const href = google_login_url+'&state='+encodeURIComponent(
l.protocol+'//'+l.hostname+':'+(l.port||80)+'?api_version=3');
window.location = href;
};
if (props.user_customers && !props.customer_selected)
{
return <Customers_form user_customers={props.user_customers}
select_final_customer={props.select_final_customer}
select_customer={props.select_customer}/>;
}
if (props.two_step)
{
return <Two_step_form verify_two_step={props.verify_two_step}
send_two_step_email={props.send_two_step_email}
email={props.username} verifying_token={props.loading}
sending_email={props.sending_email}/>;
}
return <First_form password={props.password}
username={props.username}
google_click={google_click}
save_user={props.save_user}
update_password={props.update_password}
update_username={props.update_username}/>;
};
const filter_by = (option, props)=>option.startsWith(props.text);
const Typeahead_wrapper = ({id, data, disabled, on_change, val})=>
<Typeahead id={id} options={data} maxResults={10}
minLength={0} disabled={disabled} selectHintOnEnter
filterBy={filter_by} onChange={on_change} selected={val}/>;
class Customers_form extends Pure_component {
state = {};
on_change = e=>{
this.setState({cur_customer: e});
this.props.select_customer(e&&e[0]);
};
render(){
return <T>{t=><div className="row customers_form">
<div className="warning choose_customer">
{t('Please choose a customer.')}</div>
<div className="form-group">
<label htmlFor="user_customer">{t('Customer')}</label>
<Typeahead_wrapper id="user_customer"
data={this.props.user_customers}
val={this.state.cur_customer} on_change={this.on_change}/>
</div>
<button
onClick={this.props.select_final_customer}
className="btn btn_lpm btn_login"
disabled={this.props.saving_user}>
{this.props.saving_user ? t('Logging in...') : t('Log in')}
</button>
</div>}</T>;
}
}
class Two_step_form extends Pure_component {
state = {token: ''};
componentDidMount(){
this.props.send_two_step_email();
}
on_key_up = e=>{
if (e.keyCode==13)
this.props.verify_two_step(this.state);
};
on_token_change = e=>this.setState({token: e.target.value});
render(){
const {verify_two_step, send_two_step_email, verifying_token,
sending_email, email} = this.props;
return <T>{t=><div className="row customers_form">
<div className="warning choose_customer">
2-Step Verification
</div>
{t('A Luminati 2-Step Verification email containing a token '
+'was sent to ')}
{email}
{t('. The token is valid for ')+'15 '+ t('minutes.')}
<div className="form-group">
<input className="two_step_input" onKeyUp={this.on_key_up}
onChange={this.on_token_change} placeholder={t('Token')}/>
</div>
{sending_email ?
t('Sending email...') :
<React.Fragment>
{t('Can’t find it? Check your spam folder or click ')}
<a className="link" onClick={send_two_step_email}>
{t('here')}
</a>
{t(' to resend the email.')}
</React.Fragment>
}
<button onClick={()=>verify_two_step(this.state)}
className="btn btn_lpm btn_login" disabled={verifying_token}>
{verifying_token ? t('Verifying...') : t('Verify')}
</button>
</div>}</T>;
}
}
const First_form = with_www_api(class First_form extends Pure_component {
on_key_up = e=>{
if (e.keyCode==13)
this.props.save_user();
};
render(){
const {google_click, saving_user, password, username} = this.props;
return <div className="login_form">
<T>{t=><div>
<div className="row">
<div className="col col_google col-sm-6">
<div className="btn_google_wrapper">
<a className="btn btn_lpm btn_google"
onClick={google_click}>
<div className="img"/>
{t('Log in with Google')}
</a>
</div>
</div>
<div className="col col_pass col-sm-6">
<div className="form-group">
<label htmlFor="username">{t('Email')}</label>
<input type="email"
name="username"
onChange={this.props.update_username}
onKeyUp={this.on_key_up}
value={username}/>
</div>
<div className="form-group">
<label htmlFor="user_password">{t('Password')}</label>
<input type="password"
name="password"
onChange={this.props.update_password}
onKeyUp={this.on_key_up}
value={password}/>
</div>
<button type="submit"
className="btn btn_lpm btn_login"
onClick={this.props.save_user}>
{saving_user ? t('Logging in...') : t('Log in')}
</button>
</div>
<div className="or_circle">Or</div>
</div>
<div className="row">
<div className="signup">
{t('Don\'t have a Luminati account?')}
<a href={`${this.props.www_api}/?need_signup=1`}
target="_blank"
rel="noopener noreferrer"
className="link">
{t('Sign up')}
</a>
</div>
</div>
</div>
}</T></div>;
}
});
export default Login;