Commit 3cb364bf authored by roadscape's avatar roadscape
Browse files

Merge branch 'eonwarped-steemkeychain' into bridge-api-keychain

parents 09e5463e 2451b4d2
......@@ -263,9 +263,6 @@ const mapDispatchToProps = dispatch => ({
},
});
const connectedHeader = connect(
mapStateToProps,
mapDispatchToProps
)(Header);
const connectedHeader = connect(mapStateToProps, mapDispatchToProps)(Header);
export default connectedHeader;
......@@ -6,6 +6,7 @@ import * as transactionActions from 'app/redux/TransactionReducer';
import * as globalActions from 'app/redux/GlobalReducer';
import * as userActions from 'app/redux/UserReducer';
import { validate_account_name } from 'app/utils/ChainValidation';
import { hasCompatibleKeychain } from 'app/utils/SteemKeychain';
import runTests from 'app/utils/BrowserTests';
import shouldComponentUpdate from 'app/utils/shouldComponentUpdate';
import reactForm from 'app/utils/ReactForm';
......@@ -34,7 +35,8 @@ class LoginForm extends Component {
);
cryptographyFailure = true;
}
this.state = { cryptographyFailure };
const useKeychain = hasCompatibleKeychain();
this.state = { useKeychain, cryptographyFailure };
this.usernameOnChange = e => {
const value = e.target.value.toLowerCase();
this.state.username.props.onChange(value);
......@@ -55,7 +57,7 @@ class LoginForm extends Component {
password.props.onChange(data);
});
};
this.initForm(props);
this.initForm(props, useKeychain);
}
componentDidMount() {
......@@ -67,7 +69,7 @@ class LoginForm extends Component {
shouldComponentUpdate = shouldComponentUpdate(this, 'LoginForm');
initForm(props) {
initForm(props, useKeychain) {
reactForm({
name: 'login',
instance: this,
......@@ -77,7 +79,9 @@ class LoginForm extends Component {
username: !values.username
? tt('g.required')
: validate_account_name(values.username.split('/')[0]),
password: !values.password
password: useKeychain
? null
: !values.password
? tt('g.required')
: PublicKey.fromString(values.password)
? tt('loginform_jsx.you_need_a_private_password_or_key')
......@@ -99,6 +103,11 @@ class LoginForm extends Component {
serverApiRecordEvent('SignIn', onType);
}
onUseKeychainCheckbox = e => {
const useKeychain = e.target.checked;
this.setState({ useKeychain });
};
saveLoginToggle = () => {
const { saveLogin } = this.state;
saveLoginDefault = !saveLoginDefault;
......@@ -167,7 +176,7 @@ class LoginForm extends Component {
}
const { loginBroadcastOperation, dispatchSubmit, msg } = this.props;
const { username, password, saveLogin } = this.state;
const { username, password, useKeychain, saveLogin } = this.state;
const { submitting, valid, handleSubmit } = this.state.login;
const { usernameOnChange, onCancel /*qrReader*/ } = this;
const disabled = submitting || !valid;
......@@ -254,7 +263,7 @@ class LoginForm extends Component {
}
}
const password_info =
checkPasswordChecksum(password.value) === false
!useKeychain && checkPasswordChecksum(password.value) === false
? tt('loginform_jsx.password_info')
: null;
......@@ -305,7 +314,11 @@ class LoginForm extends Component {
onSubmit={handleSubmit(({ data }) => {
// bind redux-form to react-redux
console.log('Login\tdispatchSubmit');
return dispatchSubmit(data, loginBroadcastOperation);
return dispatchSubmit(
data,
useKeychain,
loginBroadcastOperation
);
})}
onChange={this.props.clearError}
method="post"
......@@ -328,6 +341,11 @@ class LoginForm extends Component {
<div className="error">{username.error}&nbsp;</div>
) : null}
{useKeychain ? (
<div>
{error && <div className="error">{error}&nbsp;</div>}
</div>
) : (
<div>
<input
type="password"
......@@ -345,9 +363,12 @@ class LoginForm extends Component {
{error && <div className="error">{error}&nbsp;</div>}
{error &&
password_info && (
<div className="warning">{password_info}&nbsp;</div>
<div className="warning">
{password_info}&nbsp;
</div>
)}
</div>
)}
{loginBroadcastOperation && (
<div>
<div className="info">
......@@ -358,6 +379,22 @@ class LoginForm extends Component {
</div>
</div>
)}
{hasCompatibleKeychain() && (
<div>
<label
className="LoginForm__save-login"
htmlFor="useKeychain"
>
<input
id="useKeychain"
type="checkbox"
checked={useKeychain}
onChange={this.onUseKeychainCheckbox}
disabled={submitting}
/>&nbsp;{tt('loginform_jsx.use_keychain')}
</label>
</div>
)}
<div>
<label
className="LoginForm__save-login"
......@@ -487,7 +524,7 @@ export default connect(
// mapDispatchToProps
dispatch => ({
dispatchSubmit: (data, loginBroadcastOperation) => {
dispatchSubmit: (data, useKeychain, loginBroadcastOperation) => {
const { password, saveLogin } = data;
const username = data.username.trim().toLowerCase();
if (loginBroadcastOperation) {
......@@ -503,6 +540,7 @@ export default connect(
operation,
username,
password,
useKeychain,
successCallback,
errorCallback,
})
......@@ -511,6 +549,7 @@ export default connect(
userActions.usernamePasswordLogin({
username,
password,
useKeychain,
saveLogin,
operationType: type,
})
......@@ -522,6 +561,7 @@ export default connect(
userActions.usernamePasswordLogin({
username,
password,
useKeychain,
saveLogin,
})
);
......
......@@ -83,7 +83,9 @@ const SidePanel = ({ alignment, visible, hideSidePanel, username }) => {
value: 'blocktrades',
label: 'Blocktrades',
link: username
? `https://blocktrades.us/?input_coin_type=eth&output_coin_type=steem&receive_address=${username}`
? `https://blocktrades.us/?input_coin_type=eth&output_coin_type=steem&receive_address=${
username
}`
: `https://blocktrades.us/?input_coin_type=eth&output_coin_type=steem`,
},
{
......
......@@ -724,6 +724,7 @@
"returning_users": "Returning Users: ",
"login_to_wallet": "Login to Wallet",
"sign_transfer": "Sign to complete transfer",
"use_keychain": "Use keychain extension",
"dont_have_an_account": "Don't have a Steem account?"
},
"chainvalidation_js": {
......
......@@ -617,7 +617,8 @@
"sign_up_get_steem": "Crea una cuenta. Comienza en STEEM",
"signup_button": "Crea una cuenta, comienza a ganar ",
"signup_button_emphasis": "FREE STEEM!",
"returning_users": "Inicia sesion"
"returning_users": "Inicia sesion",
"use_keychain": "Use keychain extension"
},
"chainvalidation_js": {
"account_name_should": "El nombre de la cuenta debería",
......
......@@ -638,7 +638,8 @@
"sign_up_get_steem": "Sign up. Get STEEM",
"signup_button": "Sign up now to earn ",
"signup_button_emphasis": "FREE STEEM!",
"returning_users": "Utilisateurs de retour: "
"returning_users": "Utilisateurs de retour: ",
"use_keychain": "Use keychain extension"
},
"chainvalidation_js": {
"account_name_should": "Le nom du compte doit ",
......
......@@ -627,7 +627,8 @@
"sign_up_get_steem": "Sign up. Get STEEM",
"signup_button": "Sign up now to earn ",
"signup_button_emphasis": "FREE STEEM!",
"returning_users": "Utenti di ritorno:"
"returning_users": "Utenti di ritorno:",
"use_keychain": "Use keychain extension"
},
"chainvalidation_js": {
"account_name_should": "Il nome account",
......
......@@ -619,7 +619,8 @@
"signup_button": "サインアップして",
"signup_button_emphasis": "STEEMを手に入れよう!",
"sign_up_get_steem": "登録してSTEEMを手に入れよう!",
"returning_users": "再"
"returning_users": "再",
"use_keychain": "Use keychain extension"
},
"chainvalidation_js": {
"account_name_should": "アカウント名",
......
......@@ -619,7 +619,8 @@
"signup_button_emphasis": "FREE STEEM!",
"sign_up_get_steem":
"계정을 만들고 가상화폐 STEEM 을 보상으로 받으세요.",
"returning_users": "Returning Users: "
"returning_users": "Returning Users: ",
"use_keychain": "Use keychain extension"
},
"chainvalidation_js": {
"account_name_should": "Account name should ",
......
......@@ -607,7 +607,8 @@
"keep_me_logged_in": "Nie wylogowuj mnie",
"sign_up_now_to_earn": "Zarejestruj się aby zarabiać ",
"free_money": "DARMOWY STEEM!",
"returning_users": "Powracający użytkownicy: "
"returning_users": "Powracający użytkownicy: ",
"use_keychain": "Use keychain extension"
},
"chainvalidation_js": {
"account_name_should": "Nazwa konta powinna ",
......
......@@ -637,7 +637,8 @@
"sign_up_get_steem": "Sign up. Get STEEM",
"signup_button": "Зарегистрируйтесь",
"signup_button_emphasis": " сейчас!",
"returning_users": "Вернувшиеся пользователи: "
"returning_users": "Вернувшиеся пользователи: ",
"use_keychain": "Use keychain extension"
},
"chainvalidation_js": {
"account_name_should": "Имя аккаунта должно быть ",
......
......@@ -570,7 +570,8 @@
"keep_me_logged_in": "保持登录状态",
"sign_up_now_to_earn": "现在就来注册赚取",
"free_money": "免费的钱!",
"returning_users": "返回用户:"
"returning_users": "返回用户:",
"use_keychain": "Use keychain extension"
},
"chainvalidation_js": {
"account_name_should": "帐户名称应该",
......
......@@ -7,7 +7,7 @@ import { getAccount } from 'app/redux/SagaShared';
import * as userActions from 'app/redux/UserReducer';
// operations that require only posting authority
const postingOps = Set(
export const postingOps = Set(
`vote, comment, delete_comment, custom_json, claim_reward_balance`
.trim()
.split(/,\s*/)
......
......@@ -8,7 +8,7 @@ import { PrivateKey, PublicKey } from '@steemit/steem-js/lib/auth/ecc';
import { api, broadcast, auth, memo } from '@steemit/steem-js';
import { getAccount } from 'app/redux/SagaShared';
import { findSigningKey } from 'app/redux/AuthSaga';
import { postingOps, findSigningKey } from 'app/redux/AuthSaga';
import * as appActions from 'app/redux/AppReducer';
import * as globalActions from 'app/redux/GlobalReducer';
import * as transactionActions from 'app/redux/TransactionReducer';
......@@ -16,6 +16,7 @@ import * as userActions from 'app/redux/UserReducer';
import * as proposalActions from 'app/redux/ProposalReducer';
import { DEBT_TICKER } from 'app/client_config';
import { serverApiRecordEvent } from 'app/utils/ServerApiClient';
import { isLoggedInWithKeychain } from 'app/utils/SteemKeychain';
export const transactionWatches = [
takeEvery(transactionActions.BROADCAST_OPERATION, broadcastOperation),
......@@ -92,6 +93,7 @@ export function* broadcastOperation({
keys,
username,
password,
useKeychain,
successCallback,
errorCallback,
allowPostUnsafe,
......@@ -103,6 +105,7 @@ export function* broadcastOperation({
keys,
username,
password,
useKeychain,
successCallback,
errorCallback,
allowPostUnsafe,
......@@ -144,6 +147,7 @@ export function* broadcastOperation({
return;
}
try {
if (!isLoggedInWithKeychain()) {
if (!keys || keys.length === 0) {
payload.keys = [];
// user may already be logged in, or just enterend a signing passowrd or wif
......@@ -153,7 +157,8 @@ export function* broadcastOperation({
password,
});
if (signingKey) payload.keys.push(signingKey);
else if (!password) {
else {
if (!password) {
yield put(
userActions.showLogin({
operation: {
......@@ -169,6 +174,8 @@ export function* broadcastOperation({
return;
}
}
}
}
yield call(broadcastPayload, { payload });
let eventType = type
.replace(/^([a-z])/, g => g.toUpperCase())
......@@ -207,11 +214,18 @@ function hasPrivateKeys(payload) {
function* broadcastPayload({
payload: { operations, keys, username, successCallback, errorCallback },
}) {
let needsActiveAuth = false;
// console.log('broadcastPayload')
if ($STM_Config.read_only_mode) return;
for (const [type] of operations) // see also transaction/ERROR
for (const [type] of operations) {
// see also transaction/ERROR
yield put(
transactionActions.remove({ key: ['TransactionError', type] })
);
if (!postingOps.has(type)) {
needsActiveAuth = true;
}
}
{
const newOps = [];
......@@ -243,6 +257,11 @@ function* broadcastPayload({
}
};
// get username
const currentUser = yield select(state => state.user.get('current'));
const currentUsername = currentUser && currentUser.get('username');
username = username || currentUsername;
try {
yield new Promise((resolve, reject) => {
// Bump transaction (for live UI testing).. Put 0 in now (no effect),
......@@ -271,7 +290,11 @@ function* broadcastPayload({
broadcastedEvent();
}, 2000);
} else {
broadcast.send({ extensions: [], operations }, keys, err => {
if (!isLoggedInWithKeychain()) {
broadcast.send(
{ extensions: [], operations },
keys,
err => {
if (err) {
console.error(err);
reject(err);
......@@ -279,7 +302,24 @@ function* broadcastPayload({
broadcastedEvent();
resolve();
}
});
}
);
} else {
const authType = needsActiveAuth ? 'active' : 'posting';
window.steem_keychain.requestBroadcast(
username,
operations,
authType,
response => {
if (!response.success) {
reject(response.message);
} else {
broadcastedEvent();
resolve();
}
}
);
}
}
});
// status: accepted
......
......@@ -7,6 +7,11 @@ import { accountAuthLookup } from 'app/redux/AuthSaga';
import { getAccount } from 'app/redux/SagaShared';
import * as userActions from 'app/redux/UserReducer';
import { receiveFeatureFlags } from 'app/redux/AppReducer';
import {
hasCompatibleKeychain,
isLoggedInWithKeychain,
} from 'app/utils/SteemKeychain';
import { packLoginData, extractLoginData } from 'app/utils/UserUtil';
import { browserHistory } from 'react-router';
import {
serverApiLogin,
......@@ -102,7 +107,13 @@ const clean = value =>
key_types: active, owner, posting keys.
*/
function* usernamePasswordLogin({
payload: { username, password, saveLogin, operationType /*high security*/ },
payload: {
username,
password,
useKeychain,
saveLogin,
operationType /*high security*/,
},
}) {
const current = yield select(state => state.user.get('current'));
if (current) {
......@@ -126,25 +137,30 @@ function* usernamePasswordLogin({
);
// login, using saved password
let autopost, memoWif, login_owner_pubkey, login_wif_owner_pubkey;
let autopost,
memoWif,
login_owner_pubkey,
login_wif_owner_pubkey,
login_with_keychain;
if (!username && !password) {
const data = localStorage.getItem('autopost2');
if (data) {
// auto-login with a low security key (like a posting key)
autopost = true; // must use simi-colon
// The 'password' in this case must be the posting private wif .. See setItme('autopost')
[username, password, memoWif, login_owner_pubkey] = new Buffer(
data,
'hex'
)
.toString()
.split('\t');
[
username,
password,
memoWif,
login_owner_pubkey,
login_with_keychain,
] = extractLoginData(data);
memoWif = clean(memoWif);
login_owner_pubkey = clean(login_owner_pubkey);
}
}
// no saved password
if (!username || !password) {
if (!username || !(password || useKeychain || login_with_keychain)) {
console.log('No saved password');
const offchain_account = yield select(state =>
state.offchain.get('account')
......@@ -169,7 +185,25 @@ function* usernamePasswordLogin({
return;
}
// return if already logged in using steem keychain
if (login_with_keychain) {
console.log('Logged in using steem keychain');
yield put(
userActions.setUser({
username,
login_with_keychain: true,
vesting_shares: account.get('vesting_shares'),
received_vesting_shares: account.get('received_vesting_shares'),
delegated_vesting_shares: account.get(
'delegated_vesting_shares'
),
})
);
return;
}
let private_keys;
if (!useKeychain) {
try {
const private_key = PrivateKey.fromWif(password);
login_wif_owner_pubkey = private_key.toPublicKey().toString();
......@@ -181,7 +215,9 @@ function* usernamePasswordLogin({
});
} catch (e) {
// Password (non wif)
login_owner_pubkey = PrivateKey.fromSeed(username + 'owner' + password)
login_owner_pubkey = PrivateKey.fromSeed(
username + 'owner' + password
)
.toPublicKey()
.toString();
private_keys = fromJS({
......@@ -210,7 +246,7 @@ function* usernamePasswordLogin({
login_owner_pubkey,
},
});
const authority = yield select(state =>
let authority = yield select(state =>
state.user.getIn(['authority', username])
);
......@@ -255,7 +291,9 @@ function* usernamePasswordLogin({
private_keys,
login_owner_pubkey,
vesting_shares: account.get('vesting_shares'),
received_vesting_shares: account.get('received_vesting_shares'),
received_vesting_shares: account.get(
'received_vesting_shares'
),
delegated_vesting_shares: account.get(
'delegated_vesting_shares'
),
......@@ -266,16 +304,15 @@ function* usernamePasswordLogin({
userActions.setUser({
username,
vesting_shares: account.get('vesting_shares'),
received_vesting_shares: account.get('received_vesting_shares'),
received_vesting_shares: account.get(
'received_vesting_shares'
),
delegated_vesting_shares: account.get(
'delegated_vesting_shares'
),
})
);
}
if (!autopost && saveLogin) {
yield put(userActions.saveLogin());
}
try {
......@@ -287,7 +324,41 @@ function* usernamePasswordLogin({
console.log('No server account, but challenge string');
const signatures = {};
const challenge = { token: challengeString };
const bufSha = hash.sha256(JSON.stringify(challenge, null, 0));
const buf = JSON.stringify(challenge, null, 0);
const bufSha = hash.sha256(buf);
if (useKeychain) {
const response = yield new Promise(resolve => {
window.steem_keychain.requestSignBuffer(
username,
buf,
'Posting',
response => {
resolve(response);
}
);
});
if (response.success) {
signatures['posting'] = response.result;
} else {
yield put(
userActions.loginError({ error: response.message })
);
return;
}
yield put(
userActions.setUser({
username,
login_with_keychain: true,
vesting_shares: account.get('vesting_shares'),
received_vesting_shares: account.get(
'received_vesting_shares'
),
delegated_vesting_shares: account.get(
'delegated_vesting_shares'
),
})
);
} else {
const sign = (role, d) => {
console.log('Sign before');
if (!d) return;
......@@ -297,6 +368,7 @@ function* usernamePasswordLogin({
};
sign('posting', private_keys.get('posting_private'));
// sign('active', private_keys.get('active_private'))
}
console.log('Logging in as', username);
const response = yield serverApiLogin(username, signatures);
......@@ -307,12 +379,13 @@ function* usernamePasswordLogin({