@luminati-io/luminati-proxy
Version:
A configurable local proxy for brightdata.com
423 lines (410 loc) • 15 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 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 {get_location_port} from './util.js';
import {main as Api} from './api.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;
}
// XXX krzysztof: move to using customer.id once API is ready
if (this.state.customer)
creds.customer = this.state.customer.cname;
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: '
+(e.message||'could not connect');
}
_this.setState(update);
});
this.on('finally', ()=>_this.setState({loading: false}));
_this.setState({loading: true});
const res = yield Api.json.post('creds_user', creds,
{timeout: 60000});
if (res.error)
{
_this.setState({
customer: null,
user_customer_ids: null,
customer_selected: false,
error_message: res.error.message||'Something went wrong',
});
}
else if (res.customer_ids || res.ask_two_step)
{
_this.setState({
user_customer_ids: res.customer_ids,
error_message: '',
two_step: res.ask_two_step,
});
}
else
yield _this.get_in();
});
};
verify_two_step = two_step_token=>{
const _this = this;
this.etask(function*(){
_this.setState({loading: true});
const creds = {two_step_token,
two_step_pending: !_this.state.two_step_verified};
if (_this.token)
creds.token = _this.token;
const res = yield Api.json.post('creds_user', creds, {safe: true});
if (res && res.error)
{
_this.setState({loading: false, error_message:
token_err_msg[res.error.message]||token_err_msg.default});
}
else
_this.setState({two_step_verified: true}, _this.get_in);
});
};
get_in = ()=>{
const _this = this;
return this.etask(function*(){
this.on('uncaught', e=>{
_this.setState({error_message: 'Cannot sign in: '+e.message});
});
const ets = [];
ets.push(etask(function*_get_settings(){
const settings = yield Api.json.get('settings');
setdb.set('head.settings', settings);
}));
ets.push(etask(function*_get_consts(){
const consts = yield Api.json.get('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 Api.json.get('all_locations');
setdb.set('head.locations', locations);
}));
}
ets.push(etask(function*_get_zones(){
const zones = yield Api.json.get('zones');
setdb.set('ws.zones', zones);
}));
ets.push(etask(function*_get_proxies_running(){
const proxies = yield Api.json.get('proxies_running');
setdb.set('head.proxies_running', proxies);
}));
yield etask.all(ets);
_this.props.history.push('/overview');
});
};
render(){
return <div className="lum_login">
<Logo/>
<Loader show={this.state.loading}/>
<Messages
error_message={this.state.error_message}
argv={this.state.argv}
ver_node={this.state.ver_node}
/>
<Header/>
<Form
save_user={this.save_user}
user_customer_ids={this.state.user_customer_ids}
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}
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 = ()=>{
return <div className="login_header">
<h3>Login with your Bright Data account</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+':'+get_location_port()+'?api_version=3');
window.location = href;
};
if (props.user_customer_ids && !props.customer_selected)
{
return <Customers_form
user_customer_ids={props.user_customer_ids}
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}
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)=>{
return option.id && option.id.includes(props.text)
|| option.cname && option.cname.includes(props.text);
};
class Customers_form extends Pure_component {
state = {cur_customer: []};
on_change = e=>{
this.setState({cur_customer: e});
this.props.select_customer(e&&e[0]);
};
render(){
const opt = this.props.user_customer_ids
.map(e=>({id: e[0], cname: e[1]}));
return <div className="row customers_form">
<div className="warning choose_customer">
Please choose a customer</div>
<div className="form-group">
<label htmlFor="user_customer">Customer</label>
<Typeahead
id="user_customer"
options={opt}
labelKey="cname"
maxResults={10}
minLength={0}
selectHintOnEnter
filterBy={filter_by}
onChange={this.on_change}
selected={this.state.cur_customer}
/>
</div>
<button
onClick={this.props.select_final_customer}
className="btn btn_lpm btn_login"
disabled={this.props.saving_user}>
{this.props.saving_user ? 'Logging in' : 'Sign in'}
</button>
</div>;
}
}
class Two_step_form extends Pure_component {
state = {token: ''};
on_key_up = e=>{
if (e.keyCode==13)
this.submit();
};
submit = ()=>{
this.props.verify_two_step(this.state.token);
};
on_token_change = e=>this.setState({token: e.target.value});
render(){
const {verifying_token} = this.props;
return <T>{t=><div className="row customers_form">
<div className="warning choose_customer">
2-Step Verification
</div>
{t('A Bright Data 2-Step Verification email containing'
+' a token was sent to ')}
{this.props.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>
{this.props.sending_email ?
t('Sending email...') :
t('Can\'t find it? Check your spam folder')
}
<button onClick={this.submit} 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('Sign 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('Sign in')}
</button>
</div>
<div className="or_circle">Or</div>
</div>
<div className="row">
<div className="signup">
{t('Don\'t have a Bright Data account?')}
<a href={`${this.props.www_api}/?hs_signup=1`}
target="_blank"
rel="noopener noreferrer"
className="link">
{t('Sign up')}
</a>
</div>
</div>
</div>
}</T></div>;
}
});
export default Login;