sync wip: ux refactor, wallet encryption tests, wallet import/export, sync loop begin rewrite

This commit is contained in:
zeppi 2022-10-25 15:24:06 -04:00
parent bdfb8d29f5
commit d8be27aa50
13 changed files with 542 additions and 268 deletions

View file

@ -33,10 +33,10 @@ export function deriveSecrets(rootPassword, email, saltSeed, callback) {
const DEFAULT_MAXMEM = MAXMEM_MULTIPLIER * SCRYPT_N * SCRYPT_R; const DEFAULT_MAXMEM = MAXMEM_MULTIPLIER * SCRYPT_N * SCRYPT_R;
function getKeyParts(key) { function getKeyParts(key) {
const lbryIdPassword = key.slice(0, KEY_LENGTH).toString('base64'); const providerKey = key.slice(0, KEY_LENGTH).toString('base64');
const hmacKey = key.slice(KEY_LENGTH, KEY_LENGTH * 2).toString('base64'); const hmacKey = key.slice(KEY_LENGTH, KEY_LENGTH * 2).toString('base64');
const dataKey = key.slice(KEY_LENGTH * 2).toString('base64'); const dataKey = key.slice(KEY_LENGTH * 2).toString('base64');
return { lbryIdPassword, hmacKey, dataKey }; // Buffer aa bb cc 6c return { providerKey, hmacKey, dataKey }; // Buffer aa bb cc 6c
} }
const salt = generateSalt(encodedEmail, saltSeed); const salt = generateSalt(encodedEmail, saltSeed);
@ -52,8 +52,36 @@ export function deriveSecrets(rootPassword, email, saltSeed, callback) {
crypto.scrypt(encodedPassword, salt, KEY_LENGTH * NUM_KEYS, { N: SCRYPT_N, r: SCRYPT_R, p: SCRYPT_P, maxmem: DEFAULT_MAXMEM }, scryptCallback); crypto.scrypt(encodedPassword, salt, KEY_LENGTH * NUM_KEYS, { N: SCRYPT_N, r: SCRYPT_R, p: SCRYPT_P, maxmem: DEFAULT_MAXMEM }, scryptCallback);
} }
export function walletHmac(inputString) { export function walletHmac(inputString, hmacKey) {
const hmac = crypto.createHmac('sha256', inputString.toString('utf8')); const res = crypto.createHmac('sha256', hmacKey)
const res = hmac.digest('hex'); .update(inputString.toString('utf8'))
.digest('hex');
console.log('hmac res', res)
return res; return res;
} }
export function aesEncrypt(text, key) {
try {
const iv = crypto.randomBytes(16);
let cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(key), iv);
let encrypted = cipher.update(text);
encrypted = Buffer.concat([encrypted, cipher.final()]);
return { iv: iv.toString('hex'), encryptedData: encrypted.toString('hex') };
} catch (e) {
return { error: `Wallet decrypt failed error: ${e.message}` };
}
}
export function aesDecrypt(cipher, key) {
try {
let iv = Buffer.from(cipher.iv, 'hex');
let encryptedText = Buffer.from(cipher.encryptedData, 'hex');
let decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(key), iv);
let decrypted = decipher.update(encryptedText);
decrypted = Buffer.concat([decrypted, decipher.final()]);
// handle errors here
return { result: decrypted.toString() };
} catch (e) {
return { error: `Wallet decrypt failed error: ${e.message}`};
}
}

View file

@ -1,8 +1,9 @@
import test from 'tape'; import test from 'tape';
// import sync from '../sync.js'; // import sync from '../sync.js';
import { generateSalt, generateSaltSeed, deriveSecrets, walletHmac } from './sync.js'; import { generateSalt, generateSaltSeed, deriveSecrets, walletHmac, aesEncrypt, aesDecrypt } from './sync.js';
import crypto from 'crypto';
import fs from 'fs';
export default function doTest() { export default function doTest() {
test('Generate sync seed', (assert) => { test('Generate sync seed', (assert) => {
const seed = generateSaltSeed(); const seed = generateSaltSeed();
console.log('seed', seed); console.log('seed', seed);
@ -28,12 +29,13 @@ export default function doTest() {
const email = 'example@example.com'; const email = 'example@example.com';
const expectedHmacKey = 'bCxUIryLK0Lf9nKg9yiZDlGleMuGJkadLzTje1PAI+8='; //base64 const expectedHmacKey = 'bCxUIryLK0Lf9nKg9yiZDlGleMuGJkadLzTje1PAI+8='; //base64
const expectedLbryIdPassword = 'HKo/J+x4Hsy2NkMvj2JB9RI0yrvEiB4QSA/NHPaT/cA='; const expectedProviderKey = 'HKo/J+x4Hsy2NkMvj2JB9RI0yrvEiB4QSA/NHPaT/cA=';
// add expectedDataKey to test
function cb(e, r) { function cb(e, r) {
console.log('result', r); console.log('derive keys result:', r);
assert.equal(r.hmacKey, expectedHmacKey, 'hmac is expected value'); assert.equal(r.hmacKey, expectedHmacKey, 'hmac is expected value');
assert.equal(r.lbryIdPassword, expectedLbryIdPassword, 'lbryid password is expected value'); assert.equal(r.providerKey, expectedProviderKey, 'lbryid password is expected value');
assert.end(); assert.end();
} }
@ -44,13 +46,25 @@ export default function doTest() {
const hmacKey = 'bCxUIryLK0Lf9nKg9yiZDlGleMuGJkadLzTje1PAI+8='; const hmacKey = 'bCxUIryLK0Lf9nKg9yiZDlGleMuGJkadLzTje1PAI+8=';
const sequence = 1; const sequence = 1;
const walletState = `zo4MTkyOjE2OjE68QlIU76+W91/v/F1tu8h+kGB0Ee`; const walletState = `zo4MTkyOjE2OjE68QlIU76+W91/v/F1tu8h+kGB0Ee`;
const expectedHmacHex = '52edbad5b0f9d8cf6189795702790cc2cb92060be24672913ab3e4b69c03698b'; const expectedHmacHex = '9fe70ebdeaf85b3afe5ae42e52f946acc54ded0350acacdded821845217839d4';
const input_str = `${sequence}:${walletState}`; const input_str = `${sequence}:${walletState}`;
const hmacHex = walletHmac(input_str); const hmacHex = walletHmac(input_str, hmacKey);
assert.equal(hmacHex, expectedHmacHex); assert.equal(hmacHex, expectedHmacHex);
assert.end(); assert.end();
}); });
test('Encrypt/Decrypt', (assert) => {
const key = crypto.randomBytes(32);
// const wrongKey = crypto.randomBytes(32); // todo: what tests should fail; how much error handling needed?
// test a handy json file
const input = fs.readFileSync('../../package.json', 'utf8');
const cipher = aesEncrypt(input, key);
console.log('cipher', cipher);
const output = aesDecrypt(cipher, key);
assert.equal(input, output.result);
assert.end();
});
} }
doTest(); doTest();

View file

@ -2334,5 +2334,13 @@
"Doing Math": "Doing Math", "Doing Math": "Doing Math",
"Hold on, doing some math.": "Hold on, doing some math.", "Hold on, doing some math.": "Hold on, doing some math.",
"You are signed in as %email%.": "You are signed in as %email%.", "You are signed in as %email%.": "You are signed in as %email%.",
"Sync Provider Url": "Sync Provider Url",
"Enter Password": "Enter Password",
"Enter a password.": "Enter a password.",
"Some sign-in relevant message.": "Some sign-in relevant message.",
"Hold on, doing some math. (Registering you)": "Hold on, doing some math. (Registering you)",
"Failed to view lbry://@rossmanngroup#a/Day-39-Gotham-City-Solutions#e, please try again. If this problem persists, visit https://lbry.com/faq/support for support.": "Failed to view lbry://@rossmanngroup#a/Day-39-Gotham-City-Solutions#e, please try again. If this problem persists, visit https://lbry.com/faq/support for support.",
"Anon --[used in <%anonymous% Reposted>]--": "Anon",
"%anonymous%": "%anonymous%",
"--end--": "--end--" "--end--": "--end--"
} }

View file

@ -34,6 +34,7 @@ export default function HeaderMenuButtons(props: HeaderMenuButtonProps) {
<MenuList className="menu__list--header"> <MenuList className="menu__list--header">
<HeaderMenuLink page={PAGES.UPLOAD} icon={ICONS.PUBLISH} name={__('Upload')} /> <HeaderMenuLink page={PAGES.UPLOAD} icon={ICONS.PUBLISH} name={__('Upload')} />
<HeaderMenuLink page={PAGES.CHANNEL_NEW} icon={ICONS.CHANNEL} name={__('New Channel')} /> <HeaderMenuLink page={PAGES.CHANNEL_NEW} icon={ICONS.CHANNEL} name={__('New Channel')} />
<HeaderMenuLink page={PAGES.SETTINGS_SYNC} icon={ICONS.GAMING} name={__('Sign In')} />
</MenuList> </MenuList>
</Menu> </Menu>

View file

@ -123,6 +123,12 @@ export const DO_UTXO_CONSOLIDATE_STARTED = 'DO_UTXO_CONSOLIDATE_STARTED';
export const DO_UTXO_CONSOLIDATE_COMPLETED = 'DO_UTXO_CONSOLIDATE_COMPLETED'; export const DO_UTXO_CONSOLIDATE_COMPLETED = 'DO_UTXO_CONSOLIDATE_COMPLETED';
export const DO_UTXO_CONSOLIDATE_FAILED = 'DO_UTXO_CONSOLIDATE_FAILED'; export const DO_UTXO_CONSOLIDATE_FAILED = 'DO_UTXO_CONSOLIDATE_FAILED';
export const PENDING_CONSOLIDATED_TXOS_UPDATED = 'PENDING_CONSOLIDATED_TXOS_UPDATED'; export const PENDING_CONSOLIDATED_TXOS_UPDATED = 'PENDING_CONSOLIDATED_TXOS_UPDATED';
export const WALLET_EXPORT_STARTED = 'WALLET_EXPORT_STARTED';
export const WALLET_EXPORT_COMPLETED = 'WALLET_EXPORT_COMPLETED';
export const WALLET_EXPORT_FAILED = 'WALLET_EXPORT_FAILED';
export const WALLET_IMPORT_STARTED = 'WALLET_IMPORT_STARTED';
export const WALLET_IMPORT_COMPLETED = 'WALLET_IMPORT_COMPLETED';
export const WALLET_IMPORT_FAILED = 'WALLET_IMPORT_FAILED';
// Claims // Claims
export const FETCH_FEATURED_CONTENT_STARTED = 'FETCH_FEATURED_CONTENT_STARTED'; export const FETCH_FEATURED_CONTENT_STARTED = 'FETCH_FEATURED_CONTENT_STARTED';
@ -511,3 +517,9 @@ export const LSYNC_DERIVE_FAILED = 'LSYNC_DERIVE_FAILED';
export const LSYNC_GET_SALT_STARTED = 'LSYNC_GET_SALT_STARTED'; export const LSYNC_GET_SALT_STARTED = 'LSYNC_GET_SALT_STARTED';
export const LSYNC_GET_SALT_COMPLETED = 'LSYNC_GET_SALT_COMPLETED'; // got salt export const LSYNC_GET_SALT_COMPLETED = 'LSYNC_GET_SALT_COMPLETED'; // got salt
export const LSYNC_GET_SALT_FAILED = 'LSYNC_GET_SALT_FAILED'; export const LSYNC_GET_SALT_FAILED = 'LSYNC_GET_SALT_FAILED';
export const LSYNC_CHECK_EMAIL_STARTED = 'LSYNC_CHECK_EMAIL_STARTED';
export const LSYNC_CHECK_EMAIL_COMPLETED = 'LSYNC_CHECK_EMAIL_COMPLETED'; // got salt
export const LSYNC_CHECK_EMAIL_FAILED = 'LSYNC_CHECK_EMAIL_FAILED';
export const LSYNC_SYNC_STARTED = 'LSYNC_SYNC_STARTED';
export const LSYNC_SYNC_COMPLETED = 'LSYNC_SYNC_COMPLETED'; // got salt
export const LSYNC_SYNC_FAILED = 'LSYNC_SYNC_FAILED';

View file

@ -36,7 +36,7 @@ const Lbry = {
// Returns a human readable media type based on the content type or extension of a file that is returned by the sdk // Returns a human readable media type based on the content type or extension of a file that is returned by the sdk
getMediaType: (contentType: ?string, fileName: ?string) => { getMediaType: (contentType: ?string, fileName: ?string) => {
if (fileName) { if (fileName && fileName.split('.').length > 1) {
const formats = [ const formats = [
[/\.(mp4|m4v|webm|flv|f4v|ogv)$/i, 'video'], [/\.(mp4|m4v|webm|flv|f4v|ogv)$/i, 'video'],
[/\.(mp3|m4a|aac|wav|flac|ogg|opus)$/i, 'audio'], [/\.(mp3|m4a|aac|wav|flac|ogg|opus)$/i, 'audio'],
@ -111,6 +111,8 @@ const Lbry = {
wallet_list: (params = {}) => daemonCallWithResult('wallet_list', params), wallet_list: (params = {}) => daemonCallWithResult('wallet_list', params),
wallet_send: (params = {}) => daemonCallWithResult('wallet_send', params), wallet_send: (params = {}) => daemonCallWithResult('wallet_send', params),
wallet_status: (params = {}) => daemonCallWithResult('wallet_status', params), wallet_status: (params = {}) => daemonCallWithResult('wallet_status', params),
wallet_export: (params = {}) => daemonCallWithResult('wallet_export', params),
wallet_import: (params = {}) => daemonCallWithResult('wallet_import', params),
address_is_mine: (params = {}) => daemonCallWithResult('address_is_mine', params), address_is_mine: (params = {}) => daemonCallWithResult('address_is_mine', params),
address_unused: (params = {}) => daemonCallWithResult('address_unused', params), address_unused: (params = {}) => daemonCallWithResult('address_unused', params),
address_list: (params = {}) => daemonCallWithResult('address_list', params), address_list: (params = {}) => daemonCallWithResult('address_list', params),

View file

@ -4,7 +4,7 @@ const SYNC_API_DOWN = 'sync_api_down';
const DUPLICATE_EMAIL = 'duplicate_email'; const DUPLICATE_EMAIL = 'duplicate_email';
const UNKNOWN_ERROR = 'unknown_api_error'; const UNKNOWN_ERROR = 'unknown_api_error';
const NOT_FOUND = 'not_found'; const NOT_FOUND = 'not_found';
console.log('process.env.', process.env.LBRYSYNC_BASE_URL); // console.log('process.env.', process.env.LBRYSYNC_BASE_URL);
const API_VERSION = 3; const API_VERSION = 3;
const POST = 'POST'; const POST = 'POST';
@ -41,43 +41,47 @@ export async function getAuthToken(email, password, deviceId) {
export async function register(email, password, saltSeed) { export async function register(email, password, saltSeed) {
try { try {
await callWithResult(POST, REGISTER_ENDPOINT, { email, password, clientSaltSeed: saltSeed }); const result = await callWithResult(POST, REGISTER_ENDPOINT, { email, password, clientSaltSeed: saltSeed });
return; return result;
} catch (e) { } catch (e) {
return { error: e.message }; return { error: e.message };
} }
} }
export async function pushWallet(walletState, hmac, token) {
// token?
const body = {
token: token,
encryptedWallet: walletState.encryptedWallet,
sequence: walletState.sequence,
hmac: hmac,
};
await callWithResult(POST, WALLET_ENDPOINT, { token, hmac, sequence });
}
export async function pullWallet(token) { export async function pullWallet(token) {
try { try {
await callWithResult(GET, REGISTER_ENDPOINT, { token }); const result = await callWithResult(GET, WALLET_ENDPOINT, { token });
return; return result;
} catch (e) { } catch (e) {
return { error: e.message }; return { error: e.message };
} }
} // token }
// export async function pushWallet(walletState, hmac, token) {
// // token?
// const body = {
// token: token,
// encryptedWallet: walletState.encryptedWallet,
// sequence: walletState.sequence,
// hmac: hmac,
// };
// await callWithResult(POST, WALLET_ENDPOINT, { token, hmac, sequence });
// }
function callWithResult(method, endpoint, params = {}) { function callWithResult(method, endpoint, params = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
apiCall( return apiCall(
method, method,
endpoint, endpoint,
params, params,
(result) => { (result) => {
console.log('cwr result', result);
resolve(result); resolve(result);
}, },
reject (er) => {
console.log('er', er);
reject(er);
}
); );
}); });
} }
@ -96,13 +100,18 @@ function apiCall(method, endpoint, params, resolve, reject) {
return fetch(`${Lbrysync.apiUrl}${endpoint}${searchString}`, options) return fetch(`${Lbrysync.apiUrl}${endpoint}${searchString}`, options)
.then(handleResponse) .then(handleResponse)
.then((response) => { .then((response) => {
return response; console.log('response 200', response);
return resolve(response);
}) })
.catch(reject); .catch((r) => {
console.log('r', r);
return reject(r);
});
} }
function handleResponse(response) { function handleResponse(response) {
if (response.status >= 200 && response.status < 300) { if (response.status >= 200 && response.status < 300) {
console.log('200+');
return response.json(); return response.json();
} }

View file

@ -17,25 +17,18 @@ import {
selectLbrySyncEncryptedHmacKey, selectLbrySyncEncryptedHmacKey,
selectLbrySyncEncryptedRoot, selectLbrySyncEncryptedRoot,
selectLbrySyncEncryptedProviderPass, selectLbrySyncEncryptedProviderPass,
selectLbrySyncCheckingEmail,
selectLbrySyncEmailError,
selectLbrySyncEmailCandidate,
} from 'redux/selectors/sync'; } from 'redux/selectors/sync';
import { import { doHandleEmail, doLbrysyncRegister, doLbrysyncAuthenticate, doEmailVerifySubscribe } from 'redux/actions/sync';
doLbrysyncGetSalt,
doLbrysyncRegister,
doGenerateSaltSeed,
doDeriveSecrets,
doLbrysyncAuthenticate,
} from 'redux/actions/sync';
const select = (state) => ({ const select = (state) => ({
isWalletEncrypted: selectWalletIsEncrypted(state), isWalletEncrypted: selectWalletIsEncrypted(state),
registering: selectLbrySyncRegistering(state), registering: selectLbrySyncRegistering(state),
registeredEmail: selectLbrySyncEmail(state),
registerError: selectLbrySyncRegisterError(state), registerError: selectLbrySyncRegisterError(state),
gettingSalt: selectLbrySyncGettingSalt(state),
saltError: selectLbrySyncSaltError(state),
saltSeed: selectLbrySyncSaltSeed(state),
token: selectLbrySyncToken(state), token: selectLbrySyncToken(state),
authenticating: selectLbrySyncIsAuthenticating(state), authenticating: selectLbrySyncIsAuthenticating(state),
@ -46,16 +39,25 @@ const select = (state) => ({
encHmacKey: selectLbrySyncEncryptedHmacKey(state), // ? encHmacKey: selectLbrySyncEncryptedHmacKey(state), // ?
encRootPass: selectLbrySyncEncryptedRoot(state), encRootPass: selectLbrySyncEncryptedRoot(state),
encProviderPass: selectLbrySyncEncryptedProviderPass(state), encProviderPass: selectLbrySyncEncryptedProviderPass(state),
// begin
// --email
isCheckingEmail: selectLbrySyncCheckingEmail(state),
candidateEmail: selectLbrySyncEmailCandidate(state),
emailError: selectLbrySyncEmailError(state),
registeredEmail: selectLbrySyncEmail(state),
saltSeed: selectLbrySyncSaltSeed(state),
// --password
// registerError
}); });
const perform = (dispatch) => ({ const perform = (dispatch) => ({
encryptWallet: () => dispatch(doNotifyEncryptWallet()), encryptWallet: () => dispatch(doNotifyEncryptWallet()),
decryptWallet: () => dispatch(doNotifyDecryptWallet()), decryptWallet: () => dispatch(doNotifyDecryptWallet()),
getSalt: (email) => dispatch(doLbrysyncGetSalt(email)), handleEmail: (email, signUp) => dispatch(doHandleEmail(email, signUp)),
generateSaltSeed: () => dispatch(doGenerateSaltSeed()),
authenticate: () => dispatch(doLbrysyncAuthenticate()), authenticate: () => dispatch(doLbrysyncAuthenticate()),
deriveSecrets: (p, e, s) => dispatch(doDeriveSecrets(p, e, s)), waitForVerify: (stop) => dispatch(doEmailVerifySubscribe(stop)),
register: (email, secrets, saltseed) => dispatch(doLbrysyncRegister(email, secrets, saltseed)), // deriveSecrets: (p, e, s) => dispatch(doDeriveSecrets(p, e, s)),
register: (password) => dispatch(doLbrysyncRegister(password)),
}); });
export default connect(select, perform)(SettingsSync); export default connect(select, perform)(SettingsSync);

View file

@ -12,7 +12,6 @@ type Props = {
encryptWallet: (string) => void, encryptWallet: (string) => void,
decryptWallet: (string) => void, decryptWallet: (string) => void,
registering: boolean, registering: boolean,
email: string,
registerError: string, registerError: string,
token: string, token: string,
authenticating: boolean, authenticating: boolean,
@ -22,108 +21,117 @@ type Props = {
encRootPass: string, encRootPass: string,
encProviderPass: string, encProviderPass: string,
getSalt: (string) => void, getSalt: (string) => void,
gettingSalt: boolean,
saltError: string,
saltSeed: string,
deriveSecrets: (string, string, string) => void, // something deriveSecrets: (string, string, string) => void, // something
// begin
// email
handleEmail: (string, string) => void, // return something?
checkingEmail: boolean,
candidateEmail?: string,
registeredEmail?: string,
saltSeed: string,
emailError: string,
// password/register
register: (string) => void,
waitForVerify: () => void,
}; };
export default function NotificationSettingsPage(props: Props) { export default function SettingsSyncPage(props: Props) {
// const { } = props; // const { } = props;
const { const {
walletEncrypted,
encryptWallet,
decryptWallet,
registering, registering,
registeredEmail,
registerError, registerError,
token, token,
authenticating, authenticating,
authError, authError,
authenticate, authenticate,
derivingKeys,
encHmacKey, // ?
encRootPass, encRootPass,
encProviderPass,
getSalt,
generateSaltSeed,
deriveSecrets,
gettingSalt,
saltError,
saltSeed,
register, register,
// begin
// .. email
registeredEmail,
handleEmail,
checkingEmail,
candidateEmail,
saltSeed,
emailError,
// password
// verify
waitForVerify,
} = props; } = props;
/* /*
Register / auth Register / auth
*/ */
const SIGN_IN_MODE = 'sign_in';
const SIGN_UP_MODE = 'sign_up';
const VERIFY_MODE = 'verify';
const MATH_MODE = 'math';
const DONE_MODE = 'done';
const [mode, setMode] = React.useState(registeredEmail ? VERIFY_MODE : SIGN_IN_MODE); // modes
const SIGN_IN_MODE = 'sign_in_mode';
const SIGN_UP_MODE = 'sign_up_mode';
const [mode, setMode] = React.useState(SIGN_IN_MODE);
// steps
const EMAIL_SCREEN = 'sign_in'; // show email input
// checking email
const PASSWORD_SCREEN = 'password'; // show password input
// registering
const REGISTERING_SCREEN = 'register'; // show working page for deriving passwords, registering
const VERIFY_SCREEN = 'verify'; // show waiting for email verification
// waiting for email verification
const SYNC_SCREEN = 'sync';
// syncing wallet with server
const DONE_SCREEN = 'done';
const [email, setEmail] = React.useState(); const [email, setEmail] = React.useState();
const [pass, setPass] = React.useState(); const [pass, setPass] = React.useState();
const [showPass, setShowPass] = React.useState(false); const [showPass, setShowPass] = React.useState(false);
const [error, setError] = React.useState('');
let STEP;
if (!candidateEmail) {
STEP = EMAIL_SCREEN; // present email form, on submit check email salt
} else if (!encRootPass && !registering) {
// make this "hasPasswords"
STEP = PASSWORD_SCREEN; // present password form, on submit derive keys and register
} else if (registering) {
STEP = REGISTERING_SCREEN;
} else if (encRootPass && !token) {
STEP = VERIFY_SCREEN; // until token
} else if (token) {
STEP = SYNC_SCREEN;
}
// error comes from store
// const [error, setError] = React.useState('');
React.useEffect(() => { React.useEffect(() => {
let interval; if (registeredEmail && !token) {
if (!token && registeredEmail) { waitForVerify();
interval = setInterval(() => {
console.log('doauthint');
authenticate();
}, 5000);
} }
return () => { return () => {
clearInterval(interval); waitForVerify(true);
}; };
}, [registeredEmail, token, authenticate]); }, [registeredEmail, token]);
React.useEffect(() => { React.useEffect(() => {
if (token && registeredEmail) { if (token) {
setMode(DONE_MODE); // sign up
// what
// pushAndStart();
// what
//
} }
}, [registeredEmail, token, setMode]); }, [token]);
const handleSignUp = async () => { const handleRegister = (e) => {
// get salt for email to make sure register(pass);
const saltSeedOrError = await getSalt(email);
if (saltSeedOrError.seed) {
setError('Email already registered');
return;
}
// -- if found, report email already exists - sign in?
const saltSeed = await generateSaltSeed();
// saltSeed = generateSaltSeed()
setMode(MATH_MODE);
const secrets = await deriveSecrets(pass, email, saltSeed);
setMode(VERIFY_MODE);
// passwords = driveKeys(root, email, saltSeed);
try {
const registerSuccess = await register(email, secrets, saltSeed);
} catch (e) {
console.log(e);
}
// registerSuccess = register(email, servicePassword, saltSeed)
// poll auth until success
// store [token, rootPassword, providerPass, HmacKey, saltSeed, salt, registeredEmail]
}; };
const handleSignIn = async () => { const handleSignUpEmail = async () => {
// get saltseed for email // get salt for email to make sure email doesn't exist
// saltSeed = getSaltSeed() handleEmail(email, true);
// -- if error, report email not registered - sign up? };
// salt = generateSalt(seed)
// passwords = deriveKeys(root, email, saltSeed); const handleSignInEmail = () => {
// token = authenticate(email, servicePassword, deviceId) handleEmail(email, false);
// store [token, rootPassword, servicePassword, HmacKey, saltSeed, salt, registeredEmail]
// kick off sync pull
// -- possibly merge conflicts
}; };
const doneCard = ( const doneCard = (
@ -150,11 +158,19 @@ export default function NotificationSettingsPage(props: Props) {
/> />
); );
const deriveCard = ( const registerCard = (
<Card title={__('Doing Math')} subtitle={__('Hold on, doing some math.')} actions={<div>Math...</div>} /> <Card title={__('Registering')} subtitle={__('Hold on a moment, signing you up.')} actions={<div>Math...</div>} />
); );
const signInCard = ( const syncCard = (
<Card
title={__('Syncing With Server')}
subtitle={__(`Great. Now we're syncing your wallet.`)}
actions={<div>Math...</div>}
/>
);
const signInEmailCard = (
<Card <Card
title={__('Sign In')} title={__('Sign In')}
subtitle={ subtitle={
@ -172,7 +188,7 @@ export default function NotificationSettingsPage(props: Props) {
} }
actions={ actions={
<div> <div>
<Form onSubmit={handleSignIn} className="section"> <Form onSubmit={handleSignInEmail} className="section">
<FormField <FormField
autoFocus autoFocus
placeholder={__('yourstruly@example.com')} placeholder={__('yourstruly@example.com')}
@ -181,25 +197,18 @@ export default function NotificationSettingsPage(props: Props) {
label={__('Email')} label={__('Email')}
value={email} value={email}
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
helper={emailError && emailError}
/> />
<FormField <FormField
type={showPass ? 'text' : 'password'} type={'text'}
name="root_password" name="sync_provider"
inputButton={ disabled
<> label={__('Sync Provider Url')}
<Button value={'https://dev.lbry.id'}
icon={showPass ? ICONS.EYE : ICONS.EYE_OFF}
onClick={() => setShowPass(!showPass)}
className={'editable-text__input-button'}
/>
</>
}
label={__('Password')}
value={pass}
onChange={(e) => setPass(e.target.value)} onChange={(e) => setPass(e.target.value)}
/> />
<div className="section__actions"> <div className="section__actions">
<Button button="primary" type="submit" label={__('Submit')} disabled={!email} /> <Button button="primary" type="submit" label={__('Submit')} disabled={checkingEmail} />
</div> </div>
<p className="help--card-actions"> <p className="help--card-actions">
<I18nMessage <I18nMessage
@ -207,7 +216,7 @@ export default function NotificationSettingsPage(props: Props) {
terms: <Button button="link" href="https://www.lbry.com/termsofservice" label={__('terms')} />, terms: <Button button="link" href="https://www.lbry.com/termsofservice" label={__('terms')} />,
}} }}
> >
By creating an account, you agree to our %terms% and confirm you're over the age of 13. Some sign-in relevant message.
</I18nMessage> </I18nMessage>
</p> </p>
</Form> </Form>
@ -216,7 +225,7 @@ export default function NotificationSettingsPage(props: Props) {
/> />
); );
const signUpCard = ( const signUpEmailCard = (
<Card <Card
title={__('Sign Up')} title={__('Sign Up')}
subtitle={ subtitle={
@ -234,7 +243,7 @@ export default function NotificationSettingsPage(props: Props) {
} }
actions={ actions={
<div> <div>
<Form onSubmit={handleSignUp} className="section"> <Form onSubmit={handleSignUpEmail} className="section">
<FormField <FormField
autoFocus autoFocus
placeholder={__('yourstruly@example.com')} placeholder={__('yourstruly@example.com')}
@ -243,9 +252,46 @@ export default function NotificationSettingsPage(props: Props) {
label={__('Email')} label={__('Email')}
value={email} value={email}
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
helper={emailError && emailError}
/> />
<FormField <FormField
type="password" type={'text'}
name="sync_provider"
disabled
label={__('Sync Provider Url')}
value={'https://dev.lbry.id'}
onChange={(e) => setPass(e.target.value)}
/>
<div className="section__actions">
<Button button="primary" type="submit" label={__('Submit')} disabled={checkingEmail} />
</div>
</Form>
</div>
}
/>
);
const passwordCard = (
<Card
title={__('Enter Password')}
subtitle={
<>
<p>
<I18nMessage
tokens={{
sign_in: <Button button="link" onClick={() => setMode(EMAIL_SCREEN)} label={__('Sign in')} />,
}}
>
Enter a password.
</I18nMessage>
</p>
</>
}
actions={
<div>
<fieldset-section className="section">
<FormField
type={showPass ? 'text' : 'password'}
name="root_password" name="root_password"
inputButton={ inputButton={
<> <>
@ -261,7 +307,8 @@ export default function NotificationSettingsPage(props: Props) {
onChange={(e) => setPass(e.target.value)} onChange={(e) => setPass(e.target.value)}
/> />
<div className="section__actions"> <div className="section__actions">
<Button button="primary" type="submit" label={__('Submit')} disabled={!email} /> <Button button="primary" label={__('Submit')} onClick={handleRegister} />{' '}
{/* password input validation */}
</div> </div>
<p className="help--card-actions"> <p className="help--card-actions">
<I18nMessage <I18nMessage
@ -272,7 +319,7 @@ export default function NotificationSettingsPage(props: Props) {
By creating an account, you agree to our %terms% and confirm you're over the age of 13. By creating an account, you agree to our %terms% and confirm you're over the age of 13.
</I18nMessage> </I18nMessage>
</p> </p>
</Form> </fieldset-section>
</div> </div>
} }
/> />
@ -287,11 +334,12 @@ export default function NotificationSettingsPage(props: Props) {
backout={{ title: __('Wallet Sync'), backLabel: __('Back') }} backout={{ title: __('Wallet Sync'), backLabel: __('Back') }}
> >
<div className="card-stack"> <div className="card-stack">
{mode === DONE_MODE && <>{doneCard}</>} {STEP === EMAIL_SCREEN && mode === SIGN_IN_MODE && <>{signInEmailCard}</>}
{mode === SIGN_IN_MODE && <>{signInCard}</>} {STEP === EMAIL_SCREEN && mode === SIGN_UP_MODE && <>{signUpEmailCard}</>}
{mode === SIGN_UP_MODE && <>{signUpCard}</>} {STEP === PASSWORD_SCREEN && <>{passwordCard}</>}
{mode === MATH_MODE && <>{deriveCard}</>} {STEP === REGISTERING_SCREEN && <>{registerCard}</>}
{mode === VERIFY_MODE && <>{verifyCard}</>} {STEP === VERIFY_SCREEN && <>{verifyCard}</>}
{STEP === SYNC_SCREEN && <>{syncCard}</>}
</div> </div>
</Page> </Page>
); );

View file

@ -6,9 +6,9 @@ import { Lbryio } from 'lbryinc';
import Lbry from 'lbry'; import Lbry from 'lbry';
import { ipcRenderer } from 'electron'; import { ipcRenderer } from 'electron';
import * as Lbrysync from 'lbrysync'; import * as Lbrysync from 'lbrysync';
import { safeStoreEncrypt, safeStoreDecrypt, getSavedPassword } from 'util/saved-passwords'; import { safeStoreEncrypt, safeStoreDecrypt, getSavedPassword } from 'util/saved-passwords';
import { doWalletEncrypt, doWalletDecrypt } from 'redux/actions/wallet'; import { doWalletEncrypt, doWalletDecrypt, doUpdateBalance } from 'redux/actions/wallet';
import { import {
selectSyncHash, selectSyncHash,
selectGetSyncIsPending, selectGetSyncIsPending,
@ -21,6 +21,7 @@ import Comments from 'comments';
import { getSubsetFromKeysArray } from 'util/sync-settings'; import { getSubsetFromKeysArray } from 'util/sync-settings';
let syncTimer = null; let syncTimer = null;
let verifyInterval = null;
const SYNC_INTERVAL = 1000 * 60 * 5; // 5 minutes const SYNC_INTERVAL = 1000 * 60 * 5; // 5 minutes
const NO_WALLET_ERROR = 'no wallet found for this user'; const NO_WALLET_ERROR = 'no wallet found for this user';
const BAD_PASSWORD_ERROR_NAME = 'InvalidPasswordError'; const BAD_PASSWORD_ERROR_NAME = 'InvalidPasswordError';
@ -74,124 +75,188 @@ export function doSetDefaultAccount(success: () => void, failure: (string) => vo
}; };
} }
export const doLbrysyncGetSalt = (email: string) => async (dispatch: Dispatch) => { // "signupCheck=true" - if signupCheck and salt, return fail. else if salt return salt.
const { fetchSaltSeed } = Lbrysync; // export const doLbrysyncGetSalt = (email: string) => async (dispatch: Dispatch) => {
// const { fetchSaltSeed } = Lbrysync;
// dispatch({
// type: ACTIONS.LSYNC_GET_SALT_STARTED,
// });
// try {
// const saltOrError = await fetchSaltSeed(email);
// console.log('REDUX Salt', saltOrError)
// dispatch({
// type: ACTIONS.LSYNC_GET_SALT_COMPLETED,
// data: { email: email, saltSeed: saltOrError},
// });
// return saltOrError;
// } catch (e) {
// dispatch({
// type: ACTIONS.LSYNC_GET_SALT_FAILED,
// data: { email: email, saltError: 'Not Found'},
// });
// return 'not found';
// }
// };
export const doHandleEmail = (email, isSignUp) => async (dispatch: Dispatch) => {
dispatch({ dispatch({
type: ACTIONS.LSYNC_GET_SALT_STARTED, type: ACTIONS.LSYNC_CHECK_EMAIL_STARTED,
// CLEAR STUFF
}); });
try { const { fetchSaltSeed } = Lbrysync;
const saltOrError = await fetchSaltSeed(email); if (isSignUp) {
dispatch({ try {
type: ACTIONS.LSYNC_GET_SALT_COMPLETED, await fetchSaltSeed(email);
data: { email: email, saltSeed: saltOrError}, // !!got salt seed
}); dispatch({
return saltOrError; type: ACTIONS.LSYNC_CHECK_EMAIL_FAILED,
} catch (e) { data: { emailError: 'Email Already Found' },
dispatch({ });
type: ACTIONS.LSYNC_GET_SALT_FAILED, } catch (e) {
data: { email: email, saltError: 'Not Found'}, // no salt, we're good.
}); const seed = await ipcRenderer.invoke('invoke-get-salt-seed');
return 'not found'; dispatch({
type: ACTIONS.LSYNC_CHECK_EMAIL_COMPLETED,
data: { candidateEmail: email, saltSeed: seed },
});
}
} else {
// Sign In
try {
const saltResponse = await fetchSaltSeed(email);
// !!got salt seed
dispatch({
type: ACTIONS.LSYNC_CHECK_EMAIL_COMPLETED,
data: { candidateEmail: email, saltSeed: saltResponse.seed },
});
} catch (e) {
console.log('e', e.message);
dispatch({
type: ACTIONS.LSYNC_CHECK_EMAIL_FAILED,
data: { emailError: 'Email Not Found' },
});
}
} }
}; };
// register an email (eventually username) export const doLbrysyncSync = () => async (dispatch: Dispatch, getState: GetState) => {
export const doLbrysyncRegister = (email: string, secrets: any, saltSeed: string) => async (dispatch: Dispatch) => { dispatch({
const { register } = Lbrysync; type: ACTIONS.LSYNC_SYNC_STARTED,
// started });
// Wallet status
const status = await Lbry.wallet_status();
// if (status.isLocked) {
// // error
// }
// return Lbry.wallet_unlock({ password });
// .then((status) => {
//
//
// }
// See if we should pull
const { pullWallet } = Lbrysync;
// Pull from sync
const walletInfo = await pullWallet();
if (walletInfo) {
console.log('walletInfo', walletInfo);
/*
wallet_state = WalletState(
encrypted_wallet=response.json()['encryptedWallet'],
sequence=response.json()['sequence'],
)
hmac = response.json()['hmac']
return wallet_state, hmac
*/
// Lbry.wallet_import();
// update sequence, others
}
const exported = await Lbry.wallet_export();
// const encWallet = encrypt(exported, password)
};
//register an email (eventually username)
export const doLbrysyncRegister = (password: string) => async (dispatch: Dispatch, getState: GetState) => {
dispatch({ dispatch({
type: ACTIONS.LSYNC_REGISTER_STARTED, type: ACTIONS.LSYNC_REGISTER_STARTED,
}); });
const resultIfError = await register(email, secrets.providerPass, saltSeed); const state = getState();
const encProviderPass = safeStoreEncrypt(secrets.providerPass); const { sync } = state;
const encHmacKey = safeStoreEncrypt(secrets.hmacKey); const { candidateEmail: email, saltSeed } = sync;
const enctyptedRoot = safeStoreEncrypt(secrets.rootPassword); // started
const registerData = { try {
email, const secrets = await ipcRenderer.invoke('invoke-get-secrets', password, email, saltSeed);
saltSeed, const encProviderKey = safeStoreEncrypt(secrets.providerKey);
providerPass: encProviderPass, const encHmacKey = safeStoreEncrypt(secrets.hmacKey);
hmacKey: encHmacKey, const encDataKey = safeStoreEncrypt(secrets.dataKey);
rootPass: enctyptedRoot, const enctyptedRoot = safeStoreEncrypt(password);
}; const registerData = {
email, // email
if (!resultIfError) { saltSeed,
providerKey: encProviderKey,
hmacKey: encHmacKey,
dataKey: encDataKey,
rootPass: enctyptedRoot,
};
dispatch({ dispatch({
type: ACTIONS.LSYNC_REGISTER_COMPLETED, type: ACTIONS.LSYNC_REGISTER_COMPLETED,
data: registerData, data: registerData,
}); });
} else {
return registerData;
} catch (e) {
console.log('e', e.message);
dispatch({ dispatch({
type: ACTIONS.LSYNC_REGISTER_FAILED, type: ACTIONS.LSYNC_REGISTER_FAILED,
data: resultIfError, data: 'ohno',
}); });
return;
} }
}; };
// get token given username/password export function doEmailVerifySubscribe(stop) {
export const doLbrysyncAuthenticate = return (dispatch) => {
() => async (dispatch: Dispatch, getState: GetState) => { if (stop) {
dispatch({ clearInterval(verifyInterval);
type: ACTIONS.LSYNC_AUTH_STARTED,
});
const state = getState();
const { lbrysync } = state;
const { registeredEmail: email, encryptedProviderPass } = lbrysync;
const status = await Lbry.status();
const { installation_id: deviceId } = status;
const password = safeStoreDecrypt(encryptedProviderPass);
const { getAuthToken } = Lbrysync;
const result: { token?: string, error?: string } = await getAuthToken(email, password, deviceId);
if (result.token) {
dispatch({
type: ACTIONS.LSYNC_AUTH_COMPLETED,
data: result.token,
});
} else { } else {
dispatch({ dispatch(doLbrysyncAuthenticate());
type: ACTIONS.LSYNC_AUTH_FAILED, verifyInterval = setInterval(() => dispatch(doLbrysyncAuthenticate()), 5000);
data: result.error,
});
} }
}; };
}
export const doGenerateSaltSeed = () => async (dispatch: Dispatch) => { // get token given username/password
const result = await ipcRenderer.invoke('invoke-get-salt-seed'); export const doLbrysyncAuthenticate = () => async (dispatch: Dispatch, getState: GetState) => {
return result;
};
export const doDeriveSecrets = (rootPassword: string, email: string, saltSeed: string) => async (dispatch: Dispatch) =>
{
dispatch({ dispatch({
type: ACTIONS.LSYNC_DERIVE_STARTED, type: ACTIONS.LSYNC_AUTH_STARTED,
}); });
try { const state = getState();
const result = await ipcRenderer.invoke('invoke-get-secrets', rootPassword, email, saltSeed); const { sync } = state;
const { registeredEmail: email, encryptedProviderKey } = sync;
const status = await Lbry.status();
const { installation_id: deviceId } = status;
const password = safeStoreDecrypt(encryptedProviderKey);
const data = { const { getAuthToken } = Lbrysync;
hmacKey: result.hmacKey,
rootPassword,
providerPass: result.lbryIdPassword,
};
const result: { token?: string, error?: string } = await getAuthToken(email, password, deviceId);
if (result.token) {
dispatch({ dispatch({
type: ACTIONS.LSYNC_DERIVE_COMPLETED, type: ACTIONS.LSYNC_AUTH_COMPLETED,
data, data: result.token,
}); });
return data; clearInterval(verifyInterval);
} catch (e) { } else {
dispatch({ dispatch({
type: ACTIONS.LSYNC_DERIVE_FAILED, type: ACTIONS.LSYNC_AUTH_FAILED,
data: { data: result.error,
error: e,
},
}); });
return { error: e.message };
} }
}; };
// replaced with
export function doSetSync(oldHash: string, newHash: string, data: any) { export function doSetSync(oldHash: string, newHash: string, data: any) {
return (dispatch: Dispatch) => { return (dispatch: Dispatch) => {
dispatch({ dispatch({
@ -441,6 +506,54 @@ export function doSyncApply(syncHash: string, syncData: any, password: string) {
}; };
} }
export function doWalletExport(password?: string) {
return (dispatch: Dispatch) => {
dispatch({
type: ACTIONS.WALLET_EXPORT_STARTED,
});
Lbry.wallet_export({ password })
.then((walletData) => {
// if password, etc etc
// return data
dispatch({
type: ACTIONS.WALLET_EXPORT_COMPLETED,
});
})
.catch((e) => {
dispatch({
type: ACTIONS.WALLET_EXPORT_FAILED,
data: {
error: 'Wallet Export Failed',
},
});
});
};
}
export function doWalletImport(data: string, password?: string) {
return (dispatch: Dispatch) => {
dispatch({
type: ACTIONS.WALLET_IMPORT_STARTED,
});
Lbry.wallet_import({ data, password })
.then((walletData) => {
// if password, etc etc
// return data
dispatch({
type: ACTIONS.WALLET_IMPORT_COMPLETED,
});
})
.catch((e) => {
dispatch({
type: ACTIONS.WALLET_IMPORT_FAILED,
data: {
error: 'Wallet Import Failed',
},
});
});
};
}
export function doCheckSync() { export function doCheckSync() {
return (dispatch: Dispatch) => { return (dispatch: Dispatch) => {
dispatch({ dispatch({

View file

@ -19,7 +19,7 @@ export const buildSharedStateMiddleware =
const actionResult = next(action); const actionResult = next(action);
// Call `getState` after calling `next` to ensure the state has updated in response to the action // Call `getState` after calling `next` to ensure the state has updated in response to the action
function runPreferences() { function runPreferences() {
const nextState: { settings: any } = getState(); // bring in lbrysync const nextState: { settings: any } = getState(); // bring in lbrysync?
const syncEnabled = const syncEnabled =
nextState.settings && nextState.settings.clientSettings && nextState.settings.clientSettings.enable_sync; nextState.settings && nextState.settings.clientSettings && nextState.settings.clientSettings.enable_sync;
const signedIn = false; // get from new sync system newsync const signedIn = false; // get from new sync system newsync

View file

@ -17,7 +17,14 @@ const defaultState = {
hashChanged: false, hashChanged: false,
fatalError: false, fatalError: false,
// lbrysync // lbrysync
syncProvider: null, // --email
checkingEmail: false,
candidateEmail: null,
emailError: null,
//syncProvider: null,
gettingSalt: false,
saltSeed: null,
saltError: null,
// reg // reg
registering: false, registering: false,
registeredEmail: null, registeredEmail: null,
@ -31,10 +38,7 @@ const defaultState = {
encryptedHmacKey: null, encryptedHmacKey: null,
encryptedRoot: null, encryptedRoot: null,
encryptedProviderPass: null, encryptedProviderPass: null,
// salt encryptedDataKey: null,
gettingSalt: false,
saltSeed: null,
saltError: null,
}; };
reducers[ACTIONS.SYNC_STATE_POPULATE] = (state) => { reducers[ACTIONS.SYNC_STATE_POPULATE] = (state) => {
@ -136,17 +140,21 @@ reducers[ACTIONS.LSYNC_REGISTER_COMPLETED] = (state, action) => ({
...state, ...state,
registeredEmail: action.data.email, registeredEmail: action.data.email,
encryptedHmacKey: action.data.hmacKey, encryptedHmacKey: action.data.hmacKey,
encryptedProviderPass: action.data.providerPass, encryptedDataKey: action.data.dataKey,
encryptedProviderKey: action.data.providerKey,
encryptedRoot: action.data.rootPass, encryptedRoot: action.data.rootPass,
saltSeed: action.data.saltSeed, saltSeed: action.data.saltSeed,
syncProvider: action.data.syncProvider,
registering: false,
}); });
// clear attempt
reducers[ACTIONS.LSYNC_REGISTER_FAILED] = (state, action) => ({ reducers[ACTIONS.LSYNC_REGISTER_FAILED] = (state, action) => ({
...state, ...state,
registeredEmail: null, registeredEmail: null,
registering: false, registering: false,
registerError: action.data.error, registerError: action.data.error,
}); });
// Auth // Auth
reducers[ACTIONS.LSYNC_AUTH_STARTED] = (state) => ({ reducers[ACTIONS.LSYNC_AUTH_STARTED] = (state) => ({
...state, ...state,
isAuthenticating: true, isAuthenticating: true,
@ -161,12 +169,13 @@ reducers[ACTIONS.LSYNC_AUTH_FAILED] = (state, action) => ({
authError: action.data, authError: action.data,
isAuthenticating: false, isAuthenticating: false,
}); });
// derive // derive
reducers[ACTIONS.LSYNC_DERIVE_STARTED] = (state) => ({ reducers[ACTIONS.LSYNC_DERIVE_STARTED] = (state) => ({
...state, ...state,
derivingKeys: true, derivingKeys: true,
deriveError: null, deriveError: null,
}); });
// add more keys
reducers[ACTIONS.LSYNC_DERIVE_COMPLETED] = (state, action) => ({ reducers[ACTIONS.LSYNC_DERIVE_COMPLETED] = (state, action) => ({
...state, ...state,
derivingKeys: false, derivingKeys: false,
@ -176,7 +185,7 @@ reducers[ACTIONS.LSYNC_DERIVE_FAILED] = (state, action) => ({
deriveError: action.data.error, deriveError: action.data.error,
derivingKeys: false, derivingKeys: false,
}); });
// salt // salt util
reducers[ACTIONS.LSYNC_GET_SALT_STARTED] = (state) => ({ reducers[ACTIONS.LSYNC_GET_SALT_STARTED] = (state) => ({
...state, ...state,
gettingSalt: true, gettingSalt: true,
@ -191,6 +200,27 @@ reducers[ACTIONS.LSYNC_GET_SALT_FAILED] = (state, action) => ({
saltError: action.data.error, saltError: action.data.error,
gettingSalt: false, gettingSalt: false,
}); });
// email
reducers[ACTIONS.LSYNC_CHECK_EMAIL_STARTED] = (state) => ({
...state,
checkingEmail: true,
emailError: null,
candidateEmail: null,
saltSeed: null,
});
reducers[ACTIONS.LSYNC_CHECK_EMAIL_COMPLETED] = (state, action) => ({
...state,
checkingEmail: false,
candidateEmail: action.data.candidateEmail,
saltSeed: action.data.saltSeed,
});
reducers[ACTIONS.LSYNC_CHECK_EMAIL_FAILED] = (state, action) => ({
...state,
checkingEmail: false,
emailError: action.data.emailError,
candidateEmail: null,
saltSeed: null,
});
reducers[ACTIONS.SYNC_RESET] = () => defaultState; reducers[ACTIONS.SYNC_RESET] = () => defaultState;

View file

@ -1,49 +1,56 @@
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
const selectState = state => state.sync || {}; const selectState = (state) => state.sync || {};
export const selectHasSyncedWallet = createSelector(selectState, state => state.hasSyncedWallet); export const selectHasSyncedWallet = createSelector(selectState, (state) => state.hasSyncedWallet);
export const selectSyncHash = createSelector(selectState, state => state.syncHash); export const selectSyncHash = createSelector(selectState, (state) => state.syncHash);
export const selectSyncData = createSelector(selectState, state => state.syncData); export const selectSyncData = createSelector(selectState, (state) => state.syncData);
export const selectSetSyncErrorMessage = createSelector(selectState, state => state.setSyncErrorMessage); export const selectSetSyncErrorMessage = createSelector(selectState, (state) => state.setSyncErrorMessage);
export const selectGetSyncErrorMessage = createSelector(selectState, state => state.getSyncErrorMessage); export const selectGetSyncErrorMessage = createSelector(selectState, (state) => state.getSyncErrorMessage);
export const selectGetSyncIsPending = createSelector(selectState, state => state.getSyncIsPending); export const selectGetSyncIsPending = createSelector(selectState, (state) => state.getSyncIsPending);
export const selectSetSyncIsPending = createSelector(selectState, state => state.setSyncIsPending); export const selectSetSyncIsPending = createSelector(selectState, (state) => state.setSyncIsPending);
export const selectHashChanged = createSelector(selectState, state => state.hashChanged); export const selectHashChanged = createSelector(selectState, (state) => state.hashChanged);
export const selectSyncApplyIsPending = createSelector(selectState, state => state.syncApplyIsPending); export const selectSyncApplyIsPending = createSelector(selectState, (state) => state.syncApplyIsPending);
export const selectSyncApplyErrorMessage = createSelector(selectState, state => state.syncApplyErrorMessage); export const selectSyncApplyErrorMessage = createSelector(selectState, (state) => state.syncApplyErrorMessage);
export const selectSyncApplyPasswordError = createSelector(selectState, state => state.syncApplyPasswordError); export const selectSyncApplyPasswordError = createSelector(selectState, (state) => state.syncApplyPasswordError);
export const selectSyncIsLocked = createSelector(selectState, state => state.syncLocked); export const selectSyncIsLocked = createSelector(selectState, (state) => state.syncLocked);
export const selectPrefsReady = createSelector(selectState, state => state.prefsReady); export const selectPrefsReady = createSelector(selectState, (state) => state.prefsReady);
export const selectSyncFatalError = createSelector(selectState, state => state.fatalError); export const selectSyncFatalError = createSelector(selectState, (state) => state.fatalError);
// lbrysync // lbrysync
export const selectLbrySyncRegistering = createSelector(selectState, (state) => state.registering);
export const selectLbrySyncEmail = createSelector(selectState, (state) => state.registeredEmail);
export const selectLbrySyncRegisterError = createSelector(selectState, (state) => state.registerError);
// begin
export const selectLbrySyncCheckingEmail = createSelector(selectState, (state) => state.checkingEmail);
export const selectLbrySyncEmailError = createSelector(selectState, (state) => state.emailError);
export const selectLbrySyncEmail = createSelector(selectState, (state) => state.registeredEmail);
export const selectLbrySyncEmailCandidate = createSelector(selectState, (state) => state.candidateEmail);
export const selectLbrySyncGettingSalt = createSelector(selectState, (state) => state.gettingSalt); export const selectLbrySyncGettingSalt = createSelector(selectState, (state) => state.gettingSalt);
export const selectLbrySyncSaltError = createSelector(selectState, (state) => state.saltError); export const selectLbrySyncSaltError = createSelector(selectState, (state) => state.saltError);
export const selectLbrySyncSaltSeed = createSelector(selectState, (state) => state.saltSeed); export const selectLbrySyncSaltSeed = createSelector(selectState, (state) => state.saltSeed);
// password
export const selectLbrySyncIsAuthenticating = createSelector(selectState, (state) => state.isAuthenticating);
export const selectLbrySyncAuthError = createSelector(selectState, (state) => state.authError);
export const selectLbrySyncToken = createSelector(selectState, (state) => state.authToken);
export const selectLbrySyncDerivingKeys = createSelector(selectState, (state) => state.derivingKeys); export const selectLbrySyncDerivingKeys = createSelector(selectState, (state) => state.derivingKeys);
export const selectLbrySyncEncryptedHmacKey = createSelector(selectState, (state) => state.encryptedHmacKey); export const selectLbrySyncEncryptedHmacKey = createSelector(selectState, (state) => state.encryptedHmacKey);
export const selectLbrySyncEncryptedRoot = createSelector(selectState, (state) => state.encryptedRoot); export const selectLbrySyncEncryptedRoot = createSelector(selectState, (state) => state.encryptedRoot);
export const selectLbrySyncEncryptedProviderPass = createSelector(selectState, (state) => state.encryptedProviderPass); export const selectLbrySyncEncryptedProviderPass = createSelector(selectState, (state) => state.encryptedProviderPass);
// register
export const selectLbrySyncRegistering = createSelector(selectState, (state) => state.registering);
export const selectLbrySyncRegisterError = createSelector(selectState, (state) => state.registerError);
// auth
export const selectLbrySyncIsAuthenticating = createSelector(selectState, (state) => state.isAuthenticating);
export const selectLbrySyncAuthError = createSelector(selectState, (state) => state.authError);
export const selectLbrySyncToken = createSelector(selectState, (state) => state.authToken);
// push/pull?