function* uploadImage({ payload: { file, dataUrl, filename = 'image.txt', progress }, }) { const _progress = progress; progress = msg => { // console.log('Upload image progress', msg) _progress(msg); }; const stateUser = yield select(state => state.user); const username = stateUser.getIn(['current', 'username']); const d = stateUser.getIn(['current', 'private_keys', 'posting_private']); if (!username) { progress({ error: 'Please login first.' }); return; } if (!d) { progress({ error: 'Login with your posting key' }); return; } if (!file && !dataUrl) { console.error('uploadImage required: file or dataUrl'); return; } let data, dataBs64; if (file) { // drag and drop const reader = new FileReader(); data = yield new Promise(resolve => { reader.addEventListener('load', () => { const result = new Buffer(reader.result, 'binary'); resolve(result); }); reader.readAsBinaryString(file); }); } else { // recover from preview const commaIdx = dataUrl.indexOf(','); dataBs64 = dataUrl.substring(commaIdx + 1); data = new Buffer(dataBs64, 'base64'); } // The challenge needs to be prefixed with a constant (both on the server and checked on the client) to make sure the server can't easily make the client sign a transaction doing something else. const prefix = new Buffer('ImageSigningChallenge'); const bufSha = hash.sha256(Buffer.concat([prefix, data])); const formData = new FormData(); if (file) { formData.append('file', file); } else { // formData.append('file', file, filename) <- Failed to add filename=xxx to Content-Disposition // Can't easily make this look like a file so this relies on the server supporting: filename and filebinary formData.append('filename', filename); formData.append('filebase64', dataBs64); } const sig = Signature.signBufferSha256(bufSha, d); const postUrl = `${$STM_Config.upload_image}/${username}/${sig.toHex()}`; const xhr = new XMLHttpRequest(); xhr.open('POST', postUrl); xhr.onload = function() { console.log(xhr.status, xhr.responseText); const res = JSON.parse(xhr.responseText); const { error } = res; if (error) { progress({ error: 'Error: ' + error }); return; } const { url } = res; progress({ url }); }; xhr.onerror = function(error) { console.error(filename, error); progress({ error: 'Unable to contact the server.' }); }; xhr.upload.onprogress = function(event) { if (event.lengthComputable) { const percent = Math.round(event.loaded / event.total * 100); progress({ message: `Uploading ${percent}%` }); // console.log('Upload', percent) } }; xhr.send(formData); }
function* usernamePasswordLogin2({ username, password, saveLogin, operationType /*high security*/, afterLoginRedirectToWelcome, }) { // login, using saved password let feedURL = false; let autopost, memoWif, login_owner_pubkey, login_wif_owner_pubkey; 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'); memoWif = clean(memoWif); login_owner_pubkey = clean(login_owner_pubkey); } } // no saved password if (!username || !password) { const offchain_account = yield select(state => state.offchain.get('account') ); if (offchain_account) serverApiLogout(); return; } let userProvidedRole; // login via: username/owner if (username.indexOf('/') > -1) { // "alice/active" will login only with Alices active key [username, userProvidedRole] = username.split('/'); } const pathname = yield select(state => state.global.get('pathname')); const highSecurityLogin = // /owner|active/.test(userProvidedRole) || // isHighSecurityOperations.indexOf(operationType) !== -1 || highSecurityPages.find(p => p.test(pathname)) != null; const isRole = (role, fn) => !userProvidedRole || role === userProvidedRole ? fn() : undefined; const account = yield call(getAccount, username); if (!account) { yield put(userActions.loginError({ error: 'Username does not exist' })); return; } //dmca user block if (username && DMCAUserList.includes(username)) { yield put( userActions.loginError({ error: translate('terms_violation') }) ); return; } let private_keys; try { const private_key = PrivateKey.fromWif(password); login_wif_owner_pubkey = private_key.toPublicKey().toString(); private_keys = fromJS({ posting_private: isRole('posting', () => private_key), active_private: isRole('active', () => private_key), memo_private: private_key, }); } catch (e) { // Password (non wif) login_owner_pubkey = PrivateKey.fromSeed(username + 'owner' + password) .toPublicKey() .toString(); private_keys = fromJS({ posting_private: isRole('posting', () => PrivateKey.fromSeed(username + 'posting' + password) ), active_private: isRole('active', () => PrivateKey.fromSeed(username + 'active' + password) ), memo_private: PrivateKey.fromSeed(username + 'memo' + password), }); } if (memoWif) private_keys = private_keys.set( 'memo_private', PrivateKey.fromWif(memoWif) ); yield call(accountAuthLookup, { payload: { account, private_keys, highSecurityLogin, login_owner_pubkey, }, }); let authority = yield select(state => state.user.getIn(['authority', username]) ); const hasActiveAuth = authority.get('active') === 'full'; if (!highSecurityLogin) { const accountName = account.get('name'); authority = authority.set('active', 'none'); yield put(userActions.setAuthority({ accountName, auth: authority })); } const fullAuths = authority.reduce( (r, auth, type) => (auth === 'full' ? r.add(type) : r), Set() ); if (!fullAuths.size) { localStorage.removeItem('autopost2'); const owner_pub_key = account.getIn(['owner', 'key_auths', 0, 0]); if ( login_owner_pubkey === owner_pub_key || login_wif_owner_pubkey === owner_pub_key ) { yield put(userActions.loginError({ error: 'owner_login_blocked' })); } else if (!highSecurityLogin && hasActiveAuth) { yield put( userActions.loginError({ error: 'active_login_blocked' }) ); } else { const generated_type = password[0] === 'P' && password.length > 40; serverApiRecordEvent( 'login_attempt', JSON.stringify({ name: username, login_owner_pubkey, owner_pub_key, generated_type, }) ); yield put(userActions.loginError({ error: 'Incorrect Password' })); } return; } if (authority.get('posting') !== 'full') private_keys = private_keys.remove('posting_private'); if (!highSecurityLogin || authority.get('active') !== 'full') private_keys = private_keys.remove('active_private'); const owner_pubkey = account.getIn(['owner', 'key_auths', 0, 0]); const active_pubkey = account.getIn(['active', 'key_auths', 0, 0]); const posting_pubkey = account.getIn(['posting', 'key_auths', 0, 0]); if ( private_keys.get('memo_private') && account.get('memo_key') !== private_keys .get('memo_private') .toPublicKey() .toString() ) // provided password did not yield memo key private_keys = private_keys.remove('memo_private'); if (!highSecurityLogin) { if ( posting_pubkey === owner_pubkey || posting_pubkey === active_pubkey ) { yield put( userActions.loginError({ error: 'This login gives owner or active permissions and should not be used here. Please provide a posting only login.', }) ); localStorage.removeItem('autopost2'); return; } } const memo_pubkey = private_keys.has('memo_private') ? private_keys .get('memo_private') .toPublicKey() .toString() : null; if (memo_pubkey === owner_pubkey || memo_pubkey === active_pubkey) // Memo key could be saved in local storage.. In RAM it is not purged upon LOCATION_CHANGE private_keys = private_keys.remove('memo_private'); // If user is signing operation by operaion and has no saved login, don't save to RAM if (!operationType || saveLogin) { if (username) feedURL = '/@' + username + '/feed'; // Keep the posting key in RAM but only when not signing an operation. // No operation or the user has checked: Keep me logged in... yield put( userActions.setUser({ username, private_keys, login_owner_pubkey, vesting_shares: account.get('vesting_shares'), received_vesting_shares: account.get('received_vesting_shares'), delegated_vesting_shares: account.get( 'delegated_vesting_shares' ), }) ); } else { if (username) feedURL = '/@' + username + '/feed'; yield put( userActions.setUser({ username, vesting_shares: account.get('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 { // const challengeString = yield serverApiLoginChallenge() const offchainData = yield select(state => state.offchain); const serverAccount = offchainData.get('account'); const challengeString = offchainData.get('login_challenge'); if (!serverAccount && challengeString) { const signatures = {}; const challenge = { token: challengeString }; const bufSha = hash.sha256(JSON.stringify(challenge, null, 0)); const sign = (role, d) => { if (!d) return; const sig = Signature.signBufferSha256(bufSha, d); signatures[role] = sig.toHex(); }; sign('posting', private_keys.get('posting_private')); // sign('active', private_keys.get('active_private')) serverApiLogin(username, signatures); } } catch (error) { // Does not need to be fatal console.error('Server Login Error', error); } // Feature flags yield fork( getFeatureFlags, username, private_keys.get('posting_private').toString() ); if (afterLoginRedirectToWelcome) { browserHistory.push('/welcome'); } else if (feedURL) { if (document.location.pathname === '/') browserHistory.push(feedURL); } }