Merge branch 'clearnet' of https://github.com/loki-project/session-desktop into integration-test-vince

pull/1067/head
Vincent 5 years ago
commit 0a682db24a

@ -14,7 +14,7 @@ Remember, you can preview this before saving it.
### Contributor checklist: ### Contributor checklist:
* [ ] My commits are in nice logical chunks with [good commit messages](http://chris.beams.io/posts/git-commit/) * [ ] My commits are in nice logical chunks with [good commit messages](http://chris.beams.io/posts/git-commit/)
* [ ] My changes are [rebased](https://medium.freecodecamp.org/git-rebase-and-the-golden-rule-explained-70715eccc372) on the latest [`clearnet`](https://github.com/loki-project/loki-messenger/tree/development) branch * [ ] My changes are [rebased](https://blog.axosoft.com/golden-rule-of-rebasing-in-git/) on the latest [`clearnet`](https://github.com/loki-project/loki-messenger/tree/clearnet) branch
* [ ] A `yarn ready` run passes successfully ([more about tests here](https://github.com/loki-project/loki-messenger/blob/master/CONTRIBUTING.md#tests)) * [ ] A `yarn ready` run passes successfully ([more about tests here](https://github.com/loki-project/loki-messenger/blob/master/CONTRIBUTING.md#tests))
* [ ] My changes are ready to be shipped to users * [ ] My changes are ready to be shipped to users

@ -2154,7 +2154,7 @@
const shouldSendReceipt = const shouldSendReceipt =
!isError && !isError &&
data.unidentifiedDeliveryReceived && data.unidentifiedDeliveryReceived &&
!data.isFriendRequest && !data.friendRequest &&
!isGroup; !isGroup;
// Send the receipt async and hope that it succeeds // Send the receipt async and hope that it succeeds

@ -269,7 +269,7 @@ const serverRequest = async (endpoint, options = {}) => {
)); ));
} else { } else {
// disable check for .loki // disable check for .loki
process.env.NODE_TLS_REJECT_UNAUTHORIZED = url.host.match(/\.loki$/i) process.env.NODE_TLS_REJECT_UNAUTHORIZED = host.match(/\.loki$/i)
? '0' ? '0'
: '1'; : '1';
result = await nodeFetch(url, fetchOptions); result = await nodeFetch(url, fetchOptions);
@ -298,7 +298,7 @@ const serverRequest = async (endpoint, options = {}) => {
url url
); );
} }
if (mode === '_sendToProxy') { if (mode === 'sendToProxy') {
// if we can detect, certain types of failures, we can retry... // if we can detect, certain types of failures, we can retry...
if (e.code === 'ECONNRESET') { if (e.code === 'ECONNRESET') {
// retry with counter? // retry with counter?
@ -658,7 +658,7 @@ class LokiAppDotNetServerAPI {
try { try {
const res = await this.proxyFetch( const res = await this.proxyFetch(
`${this.baseServerUrl}/loki/v1/submit_challenge`, new URL(`${this.baseServerUrl}/loki/v1/submit_challenge`),
fetchOptions, fetchOptions,
{ textResponse: true } { textResponse: true }
); );
@ -683,7 +683,8 @@ class LokiAppDotNetServerAPI {
} }
const urlStr = urlObj.toString(); const urlStr = urlObj.toString();
const endpoint = urlStr.replace(`${this.baseServerUrl}/`, ''); const endpoint = urlStr.replace(`${this.baseServerUrl}/`, '');
const { response, result } = await this._sendToProxy( const { response, result } = await sendToProxy(
this.pubKey,
endpoint, endpoint,
finalOptions, finalOptions,
options options
@ -694,8 +695,8 @@ class LokiAppDotNetServerAPI {
json: () => response, json: () => response,
}; };
} }
const urlStr = urlObj.toString(); const host = urlObj.host.toLowerCase();
if (urlStr.match(/\.loki\//)) { if (host.match(/\.loki$/)) {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
} }
const result = nodeFetch(urlObj, fetchOptions, options); const result = nodeFetch(urlObj, fetchOptions, options);

@ -206,6 +206,18 @@ class LokiMessageAPI {
targetNode targetNode
); );
// do not return true if we get false here...
if (result === false) {
log.warn(
`loki_message:::_sendToNode - Got false from ${targetNode.ip}:${
targetNode.port
}`
);
successiveFailures += 1;
// eslint-disable-next-line no-continue
continue;
}
// Make sure we aren't doing too much PoW // Make sure we aren't doing too much PoW
const currentDifficulty = window.storage.get('PoWDifficulty', null); const currentDifficulty = window.storage.get('PoWDifficulty', null);
if ( if (
@ -387,6 +399,15 @@ class LokiMessageAPI {
nodeData nodeData
); );
if (result === false) {
// make a note of it because of caller doesn't care...
log.warn(
`loki_message:::_retrieveNextMessages - lokiRpc returned false to ${
nodeData.ip
}:${nodeData.port}`
);
}
return result.messages || []; return result.messages || [];
} }

@ -192,14 +192,24 @@ const processOnionResponse = async (reqIdx, response, sharedKey, useAesGcm) => {
const sendToProxy = async (options = {}, targetNode, retryNumber = 0) => { const sendToProxy = async (options = {}, targetNode, retryNumber = 0) => {
const _ = window.Lodash; const _ = window.Lodash;
const snodePool = await lokiSnodeAPI.getRandomSnodePool(); let snodePool = await lokiSnodeAPI.getRandomSnodePool();
if (snodePool.length < 2) { if (snodePool.length < 2) {
log.error( log.error(
'Not enough service nodes for a proxy request, only have: ', 'lokiRpc::sendToProxy - Not enough service nodes for a proxy request, only have:',
snodePool.length snodePool.length,
'snode, attempting refresh'
); );
return false; await lokiSnodeAPI.refreshRandomPool();
snodePool = await lokiSnodeAPI.getRandomSnodePool();
if (snodePool.length < 2) {
log.error(
'lokiRpc::sendToProxy - Not enough service nodes for a proxy request, only have:',
snodePool.length,
'failing'
);
return false;
}
} }
// Making sure the proxy node is not the same as the target node: // Making sure the proxy node is not the same as the target node:
@ -292,7 +302,11 @@ const sendToProxy = async (options = {}, targetNode, retryNumber = 0) => {
// it's likely a net problem or an actual problem on the target node // it's likely a net problem or an actual problem on the target node
// lets mark the target node bad for now // lets mark the target node bad for now
// we'll just rotate it back in if it's a net problem // we'll just rotate it back in if it's a net problem
log.warn(`Failing ${targetNode.ip}:${targetNode.port} after 5 retries`); log.warn(
`lokiRpc:::sendToProxy - Failing ${targetNode.ip}:${
targetNode.port
} after 5 retries`
);
if (options.ourPubKey) { if (options.ourPubKey) {
lokiSnodeAPI.unreachableNode(options.ourPubKey, targetNode); lokiSnodeAPI.unreachableNode(options.ourPubKey, targetNode);
} }
@ -329,7 +343,11 @@ const sendToProxy = async (options = {}, targetNode, retryNumber = 0) => {
// avoid base64 decode failure // avoid base64 decode failure
// usually a 500 but not always // usually a 500 but not always
// could it be a timeout? // could it be a timeout?
log.warn('Server did not return any data for', options, targetNode); log.warn(
'lokiRpc:::sendToProxy - Server did not return any data for',
options,
targetNode
);
return false; return false;
} }
@ -404,6 +422,7 @@ const sendToProxy = async (options = {}, targetNode, retryNumber = 0) => {
}; };
// A small wrapper around node-fetch which deserializes response // A small wrapper around node-fetch which deserializes response
// returns nodeFetch response or false
const lokiFetch = async (url, options = {}, targetNode = null) => { const lokiFetch = async (url, options = {}, targetNode = null) => {
const timeout = options.timeout || 10000; const timeout = options.timeout || 10000;
const method = options.method || 'GET'; const method = options.method || 'GET';
@ -454,6 +473,22 @@ const lokiFetch = async (url, options = {}, targetNode = null) => {
if (window.lokiFeatureFlags.useSnodeProxy && targetNode) { if (window.lokiFeatureFlags.useSnodeProxy && targetNode) {
const result = await sendToProxy(fetchOptions, targetNode); const result = await sendToProxy(fetchOptions, targetNode);
if (result === false) {
// should we retry?
log.warn(`lokiRpc:::lokiFetch - sendToProxy returned false`);
// one case is:
// snodePool didn't have enough
// even after a refresh
// likely a network disconnect?
// but not all cases...
/*
log.warn(
'lokiRpc:::lokiFetch - useSnodeProxy failure, could not refresh randomPool, offline?'
);
*/
// pass the false value up
return false;
}
// if not result, maybe we should throw?? // if not result, maybe we should throw??
return result ? result.json() : {}; return result ? result.json() : {};
} }

@ -97,18 +97,36 @@ class LokiSnodeAPI {
async selectGuardNodes() { async selectGuardNodes() {
const _ = window.Lodash; const _ = window.Lodash;
const nodePool = await this.getRandomSnodePool(); let nodePool = await this.getRandomSnodePool();
if (nodePool.length === 0) { if (nodePool.length === 0) {
log.error(`Could not select guarn nodes: node pool is empty`); log.error(`Could not select guarn nodes: node pool is empty`);
return []; return [];
} }
const shuffled = _.shuffle(nodePool); let shuffled = _.shuffle(nodePool);
let guardNodes = []; let guardNodes = [];
const DESIRED_GUARD_COUNT = 3; const DESIRED_GUARD_COUNT = 3;
if (shuffled.length < DESIRED_GUARD_COUNT) {
log.error(
`Could not select guarn nodes: node pool is not big enough, pool size ${
shuffled.length
}, need ${DESIRED_GUARD_COUNT}, attempting to refresh randomPool`
);
await this.refreshRandomPool();
nodePool = await this.getRandomSnodePool();
shuffled = _.shuffle(nodePool);
if (shuffled.length < DESIRED_GUARD_COUNT) {
log.error(
`Could not select guarn nodes: node pool is not big enough, pool size ${
shuffled.length
}, need ${DESIRED_GUARD_COUNT}, failing...`
);
return [];
}
}
// The use of await inside while is intentional: // The use of await inside while is intentional:
// we only want to repeat if the await fails // we only want to repeat if the await fails
@ -490,6 +508,7 @@ class LokiSnodeAPI {
throw new window.textsecure.SeedNodeError('Failed to contact seed node'); throw new window.textsecure.SeedNodeError('Failed to contact seed node');
} }
log.info('loki_snodes:::refreshRandomPoolPromise - RESOLVED'); log.info('loki_snodes:::refreshRandomPoolPromise - RESOLVED');
delete this.refreshRandomPoolPromise; // clear any lock
} }
// unreachableNode.url is like 9hrje1bymy7hu6nmtjme9idyu3rm8gr3mkstakjyuw1997t7w4ny.snode // unreachableNode.url is like 9hrje1bymy7hu6nmtjme9idyu3rm8gr3mkstakjyuw1997t7w4ny.snode

@ -165,9 +165,9 @@
props: { props: {
titleText: this.titleText, titleText: this.titleText,
groupName: this.groupName, groupName: this.groupName,
okText: this.okText, okText: i18n('ok'),
cancelText: i18n('cancel'),
isPublic: this.isPublic, isPublic: this.isPublic,
cancelText: this.cancelText,
existingMembers: this.existingMembers, existingMembers: this.existingMembers,
friendList: this.friendsAndMembers, friendList: this.friendsAndMembers,
isAdmin: this.isAdmin, isAdmin: this.isAdmin,

@ -198,10 +198,10 @@ function captureClicks(window) {
window.webContents.on('new-window', handleUrl); window.webContents.on('new-window', handleUrl);
} }
const DEFAULT_WIDTH = 800; const DEFAULT_WIDTH = 880;
const DEFAULT_HEIGHT = 720; const DEFAULT_HEIGHT = 720;
const MIN_WIDTH = 880; const MIN_WIDTH = 880;
const MIN_HEIGHT = 580; const MIN_HEIGHT = 720;
const BOUNDS_BUFFER = 100; const BOUNDS_BUFFER = 100;
function isVisible(window, bounds) { function isVisible(window, bounds) {
@ -617,7 +617,7 @@ async function showDebugLogWindow() {
return; return;
} }
const theme = await pify(getDataFromMainWindow)('theme-setting'); const theme = await getThemeFromMainWindow();
const size = mainWindow.getSize(); const size = mainWindow.getSize();
const options = { const options = {
width: Math.max(size[0] - 100, MIN_WIDTH), width: Math.max(size[0] - 100, MIN_WIDTH),
@ -665,7 +665,7 @@ async function showPermissionsPopupWindow() {
return; return;
} }
const theme = await pify(getDataFromMainWindow)('theme-setting'); const theme = await getThemeFromMainWindow();
const size = mainWindow.getSize(); const size = mainWindow.getSize();
const options = { const options = {
width: Math.min(400, size[0]), width: Math.min(400, size[0]),
@ -1130,9 +1130,9 @@ ipc.on('set-auto-update-setting', (event, enabled) => {
} }
}); });
function getDataFromMainWindow(name, callback) { function getThemeFromMainWindow() {
ipc.once(`get-success-${name}`, (_event, error, value) => return new Promise(resolve => {
callback(error, value) ipc.once('get-success-theme-setting', (_event, value) => resolve(value));
); mainWindow.webContents.send('get-theme-setting');
mainWindow.webContents.send(`get-${name}`); });
} }

@ -2,7 +2,7 @@
"name": "session-messenger-desktop", "name": "session-messenger-desktop",
"productName": "Session", "productName": "Session",
"description": "Private messaging from your desktop", "description": "Private messaging from your desktop",
"version": "1.0.4", "version": "1.0.5",
"license": "GPL-3.0", "license": "GPL-3.0",
"author": { "author": {
"name": "Loki Project", "name": "Loki Project",

@ -197,6 +197,11 @@ ipc.on('set-up-as-standalone', () => {
Whisper.events.trigger('setupAsStandalone'); Whisper.events.trigger('setupAsStandalone');
}); });
ipc.on('get-theme-setting', () => {
const theme = window.Events.getThemeSetting();
ipc.send('get-success-theme-setting', theme);
});
// Settings-related events // Settings-related events
window.showPermissionsPopup = () => ipc.send('show-permissions-popup'); window.showPermissionsPopup = () => ipc.send('show-permissions-popup');

@ -82,7 +82,7 @@
.error-faded { .error-faded {
opacity: 0; opacity: 0;
margin-top: -20px; margin-top: -5px;
transition: all 100ms linear; transition: all 100ms linear;
} }
@ -96,8 +96,6 @@
max-height: 240px; max-height: 240px;
overflow-y: auto; overflow-y: auto;
margin: 4px; margin: 4px;
border-top: 1px solid #2f2f2f;
border-bottom: 1px solid #2f2f2f;
.check-mark { .check-mark {
float: right; float: right;

@ -1,50 +0,0 @@
.password {
.content-wrapper {
display: flex;
align-items: center;
justify-content: center;
color: $color-dark-05;
width: 100%;
height: 100%;
}
.content {
margin: 3em;
}
.inputs {
display: flex;
flex-direction: column;
}
input {
width: 30em;
}
.error {
font-weight: bold;
font-size: 16px;
margin-top: 1em;
}
.reset {
font-size: 15px;
margin-top: 1em;
cursor: pointer;
user-select: none;
a {
color: #78be20;
font-weight: bold;
}
}
.overlay {
color: $color-dark-05;
background: $color-dark-85;
.step {
padding: 0;
}
}
}

@ -140,11 +140,11 @@ div.spacer-lg {
transition: filter 0.1s; transition: filter 0.1s;
} }
.text-subtle { .subtle {
opacity: 0.6; opacity: 0.6;
} }
.text-soft { .soft {
opacity: 0.4; opacity: 0.4;
} }
@ -848,6 +848,12 @@ label {
.friend-selection-list { .friend-selection-list {
width: unset; width: unset;
} }
.create-group-dialog__member-count {
text-align: center;
margin-top: -25px;
opacity: 0.6;
}
} }
.session-confirm { .session-confirm {
@ -963,7 +969,7 @@ label {
list-style: none; list-style: none;
padding: 0px; padding: 0px;
margin: 0px; margin: 0px;
max-height: 450px; max-height: 40vh;
overflow-y: auto; overflow-y: auto;
} }
@ -1636,92 +1642,6 @@ input {
} }
} }
.clear-data,
.password-prompt {
&-wrapper {
display: flex;
justify-content: center;
align-items: center;
background-color: $session-color-black;
width: 100%;
height: 100%;
padding: 3 * $session-margin-lg;
}
&-error-section {
width: 100%;
color: $session-color-white;
margin: -$session-margin-sm 0px 2 * $session-margin-lg 0px;
.session-label {
&.primary {
background-color: rgba($session-color-primary, 0.3);
}
padding: $session-margin-xs $session-margin-sm;
font-size: $session-font-xs;
text-align: center;
}
}
&-container {
font-family: 'SF Pro Text';
color: $session-color-white;
display: inline-flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 600px;
min-width: 420px;
padding: 3 * $session-margin-lg 2 * $session-margin-lg;
box-sizing: border-box;
background-color: $session-shade-4;
border: 1px solid $session-shade-8;
border-radius: 2px;
.warning-info-area,
.password-info-area {
display: inline-flex;
justify-content: center;
align-items: center;
h1 {
color: $session-color-white;
}
svg {
margin-right: $session-margin-lg;
}
}
p,
input {
margin: $session-margin-lg 0px;
}
.button-group {
display: inline-flex;
}
#password-prompt-input {
width: 100%;
color: #fff;
background-color: #2e2e2e;
margin-top: 2 * $session-margin-lg;
padding: $session-margin-xs $session-margin-lg;
outline: none;
border: none;
border-radius: 2px;
text-align: center;
font-size: 24px;
letter-spacing: 5px;
font-family: 'SF Pro Text';
}
}
}
.onboarding-message-section { .onboarding-message-section {
display: flex; display: flex;
flex-grow: 1; flex-grow: 1;
@ -1845,6 +1765,13 @@ input {
justify-content: space-between; justify-content: space-between;
transition: $session-transition-duration; transition: $session-transition-duration;
&:first-child {
border-top: 1px solid rgba($session-shade-8, 0.6);
}
&:last-child {
border-bottom: 1px solid rgba($session-shade-8, 0.6);
}
&.selected { &.selected {
background-color: $session-shade-4; background-color: $session-shade-4;
} }
@ -1872,6 +1799,10 @@ input {
margin-left: 5px; margin-left: 5px;
opacity: 0.8; opacity: 0.8;
} }
&__avatar > div {
margin-bottom: 0px !important;
}
} }
.invite-friends-container { .invite-friends-container {

@ -0,0 +1,89 @@
.password {
height: 100vh;
.clear-data,
.password-prompt {
&-wrapper {
display: flex;
justify-content: center;
align-items: center;
background-color: $session-color-black;
width: 100%;
height: 100%;
padding: 3 * $session-margin-lg;
}
&-error-section {
width: 100%;
color: $session-color-white;
margin: -$session-margin-sm 0px 2 * $session-margin-lg 0px;
.session-label {
&.primary {
background-color: rgba($session-color-primary, 0.3);
}
padding: $session-margin-xs $session-margin-sm;
font-size: $session-font-xs;
text-align: center;
}
}
&-container {
font-family: 'SF Pro Text';
color: $session-color-white;
display: inline-flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 600px;
min-width: 420px;
padding: 3 * $session-margin-lg 2 * $session-margin-lg;
box-sizing: border-box;
background-color: $session-shade-4;
border: 1px solid $session-shade-8;
border-radius: 2px;
.warning-info-area,
.password-info-area {
display: inline-flex;
justify-content: center;
align-items: center;
h1 {
color: $session-color-white;
}
svg {
margin-right: $session-margin-lg;
}
}
p,
input {
margin: $session-margin-lg 0px;
}
.button-group {
display: inline-flex;
}
#password-prompt-input {
width: 100%;
color: #fff;
background-color: #2e2e2e;
margin-top: 2 * $session-margin-lg;
padding: $session-margin-xs $session-margin-lg;
outline: none;
border: none;
border-radius: 2px;
text-align: center;
font-size: 24px;
letter-spacing: 5px;
font-family: 'SF Pro Text';
}
}
}
}

@ -12,7 +12,6 @@
@import 'emoji'; @import 'emoji';
@import 'mentions'; @import 'mentions';
@import 'settings'; @import 'settings';
@import 'password';
// Build the main view // Build the main view
@import 'index'; @import 'index';
@ -22,10 +21,16 @@
@import 'ios'; @import 'ios';
@import 'theme_dark'; @import 'theme_dark';
// Session // /////////////////// //
// ///// Session ///// //
// /////////////////// //
@import 'modules'; @import 'modules';
@import 'session'; @import 'session';
// Separate screens
@import 'session_signin'; @import 'session_signin';
@import 'session_password';
@import 'session_theme'; @import 'session_theme';
@import 'session_left_pane'; @import 'session_left_pane';
@import 'session_group_panel'; @import 'session_group_panel';

@ -120,7 +120,7 @@ export class DevicePairingDialog extends React.Component<Props, State> {
<div className="device-pairing-dialog__secret-words"> <div className="device-pairing-dialog__secret-words">
<label>{window.i18n('secretWords')}</label> <label>{window.i18n('secretWords')}</label>
<div className="text-subtle">{secretWords}</div> <div className="subtle">{secretWords}</div>
</div> </div>
<div className="session-modal__button-group"> <div className="session-modal__button-group">
@ -148,9 +148,7 @@ export class DevicePairingDialog extends React.Component<Props, State> {
<div className="session-modal__centered"> <div className="session-modal__centered">
{this.renderErrors()} {this.renderErrors()}
<h4>{window.i18n('waitingForDeviceToRegister')}</h4> <h4>{window.i18n('waitingForDeviceToRegister')}</h4>
<small className="text-subtle"> <small className="subtle">{window.i18n('pairNewDevicePrompt')}</small>
{window.i18n('pairNewDevicePrompt')}
</small>
<div className="spacer-lg" /> <div className="spacer-lg" />
<div className="qr-image"> <div className="qr-image">
@ -199,7 +197,7 @@ export class DevicePairingDialog extends React.Component<Props, State> {
<p className="session-modal__description"> <p className="session-modal__description">
{window.i18n('confirmUnpairingTitle')} {window.i18n('confirmUnpairingTitle')}
<br /> <br />
<span className="text-subtle">{description}</span> <span className="subtle">{description}</span>
</p> </p>
<div className="spacer-xs" /> <div className="spacer-xs" />
<div className="session-modal__button-group"> <div className="session-modal__button-group">

@ -93,7 +93,7 @@ export class UpdateGroupMembersDialog extends React.Component<Props, State> {
noFriendsClasses = classNames('no-friends', 'hidden'); noFriendsClasses = classNames('no-friends', 'hidden');
} else { } else {
// private group // private group
titleText = `${this.props.titleText} (Members: ${checkMarkedCount})`; titleText = this.props.titleText;
noFriendsClasses = noFriendsClasses =
this.state.friendList.length === 0 this.state.friendList.length === 0
? 'no-friends' ? 'no-friends'
@ -114,6 +114,16 @@ export class UpdateGroupMembersDialog extends React.Component<Props, State> {
onOk={() => null} onOk={() => null}
> >
<div className="spacer-md" /> <div className="spacer-md" />
{!this.props.isPublic && (
<>
<small className="create-group-dialog__member-count">
{`${checkMarkedCount} members`}
</small>
<hr className="subtle" />
</>
)}
<p className={errorMessageClasses}>{errorMsg}</p> <p className={errorMessageClasses}>{errorMsg}</p>
<div className="spacer-md" /> <div className="spacer-md" />
@ -124,6 +134,8 @@ export class UpdateGroupMembersDialog extends React.Component<Props, State> {
'noMembersInThisGroup' 'noMembersInThisGroup'
)})`}</p> )})`}</p>
<div className="spacer-lg" />
<div className="session-modal__button-group"> <div className="session-modal__button-group">
<SessionButton text={okText} onClick={this.onClickOK} /> <SessionButton text={okText} onClick={this.onClickOK} />

@ -39,6 +39,7 @@ interface State {
passwordErrorString: string; passwordErrorString: string;
passwordFieldsMatch: boolean; passwordFieldsMatch: boolean;
mnemonicSeed: string; mnemonicSeed: string;
generatedMnemonicSeed: string;
hexGeneratedPubKey: string; hexGeneratedPubKey: string;
primaryDevicePubKey: string; primaryDevicePubKey: string;
mnemonicError: string | undefined; mnemonicError: string | undefined;
@ -113,6 +114,7 @@ export class RegistrationTabs extends React.Component<{}, State> {
passwordErrorString: '', passwordErrorString: '',
passwordFieldsMatch: false, passwordFieldsMatch: false,
mnemonicSeed: '', mnemonicSeed: '',
generatedMnemonicSeed: '',
hexGeneratedPubKey: '', hexGeneratedPubKey: '',
primaryDevicePubKey: '', primaryDevicePubKey: '',
mnemonicError: undefined, mnemonicError: undefined,
@ -125,39 +127,11 @@ export class RegistrationTabs extends React.Component<{}, State> {
window.textsecure.storage.remove('secondaryDeviceStatus'); window.textsecure.storage.remove('secondaryDeviceStatus');
} }
public render() { public componentDidMount() {
this.generateMnemonicAndKeyPair().ignore(); this.generateMnemonicAndKeyPair().ignore();
return this.renderTabs();
}
private async generateMnemonicAndKeyPair() {
if (this.state.mnemonicSeed === '') {
const language = 'english';
const mnemonic = await this.accountManager.generateMnemonic(language);
let seedHex = window.mnemonic.mn_decode(mnemonic, language);
// handle shorter than 32 bytes seeds
const privKeyHexLength = 32 * 2;
if (seedHex.length !== privKeyHexLength) {
seedHex = seedHex.concat(seedHex);
seedHex = seedHex.substring(0, privKeyHexLength);
}
const seed = window.dcodeIO.ByteBuffer.wrap(
seedHex,
'hex'
).toArrayBuffer();
const keyPair = await window.libsignal.Curve.async.createKeyPair(seed);
const hexGeneratedPubKey = Buffer.from(keyPair.pubKey).toString('hex');
this.setState({
mnemonicSeed: mnemonic,
hexGeneratedPubKey, // our 'frontend' sessionID
});
}
} }
private renderTabs() { public render() {
const { selectedTab } = this.state; const { selectedTab } = this.state;
const createAccount = window.i18n('createAccount'); const createAccount = window.i18n('createAccount');
@ -186,6 +160,32 @@ export class RegistrationTabs extends React.Component<{}, State> {
); );
} }
private async generateMnemonicAndKeyPair() {
if (this.state.generatedMnemonicSeed === '') {
const language = 'english';
const mnemonic = await this.accountManager.generateMnemonic(language);
let seedHex = window.mnemonic.mn_decode(mnemonic, language);
// handle shorter than 32 bytes seeds
const privKeyHexLength = 32 * 2;
if (seedHex.length !== privKeyHexLength) {
seedHex = seedHex.concat(seedHex);
seedHex = seedHex.substring(0, privKeyHexLength);
}
const seed = window.dcodeIO.ByteBuffer.wrap(
seedHex,
'hex'
).toArrayBuffer();
const keyPair = await window.libsignal.Curve.async.createKeyPair(seed);
const hexGeneratedPubKey = Buffer.from(keyPair.pubKey).toString('hex');
this.setState({
generatedMnemonicSeed: mnemonic,
hexGeneratedPubKey, // our 'frontend' sessionID
});
}
}
private readonly handleTabSelect = (tabType: TabType): void => { private readonly handleTabSelect = (tabType: TabType): void => {
if (tabType !== TabType.SignIn) { if (tabType !== TabType.SignIn) {
this.cancelSecondaryDevice().ignore(); this.cancelSecondaryDevice().ignore();
@ -200,7 +200,6 @@ export class RegistrationTabs extends React.Component<{}, State> {
passwordErrorString: '', passwordErrorString: '',
passwordFieldsMatch: false, passwordFieldsMatch: false,
mnemonicSeed: '', mnemonicSeed: '',
hexGeneratedPubKey: '',
primaryDevicePubKey: '', primaryDevicePubKey: '',
mnemonicError: undefined, mnemonicError: undefined,
displayNameError: undefined, displayNameError: undefined,
@ -731,15 +730,19 @@ export class RegistrationTabs extends React.Component<{}, State> {
const { const {
password, password,
mnemonicSeed, mnemonicSeed,
generatedMnemonicSeed,
signInMode,
displayName, displayName,
passwordErrorString, passwordErrorString,
passwordFieldsMatch, passwordFieldsMatch,
} = this.state; } = this.state;
// Make sure the password is valid // Make sure the password is valid
window.log.info('starting registration');
const trimName = displayName.trim(); const trimName = displayName.trim();
if (!trimName) { if (!trimName) {
window.log.warn('invalid trimmed name for registration');
window.pushToast({ window.pushToast({
title: window.i18n('displayNameEmpty'), title: window.i18n('displayNameEmpty'),
type: 'error', type: 'error',
@ -750,6 +753,7 @@ export class RegistrationTabs extends React.Component<{}, State> {
} }
if (passwordErrorString) { if (passwordErrorString) {
window.log.warn('invalid password for registration');
window.pushToast({ window.pushToast({
title: window.i18n('invalidPassword'), title: window.i18n('invalidPassword'),
type: 'error', type: 'error',
@ -760,6 +764,8 @@ export class RegistrationTabs extends React.Component<{}, State> {
} }
if (!!password && !passwordFieldsMatch) { if (!!password && !passwordFieldsMatch) {
window.log.warn('passwords does not match for registration');
window.pushToast({ window.pushToast({
title: window.i18n('passwordsDoNotMatch'), title: window.i18n('passwordsDoNotMatch'),
type: 'error', type: 'error',
@ -769,28 +775,48 @@ export class RegistrationTabs extends React.Component<{}, State> {
return; return;
} }
if (!mnemonicSeed) { if (signInMode === SignInMode.UsingSeed && !mnemonicSeed) {
window.log.warn('empty mnemonic seed passed in seed restoration mode');
return;
} else if (!generatedMnemonicSeed) {
window.log.warn('empty generated seed');
return; return;
} }
// Ensure we clear the secondary device registration status // Ensure we clear the secondary device registration status
window.textsecure.storage.remove('secondaryDeviceStatus'); window.textsecure.storage.remove('secondaryDeviceStatus');
const seedToUse =
signInMode === SignInMode.UsingSeed
? mnemonicSeed
: generatedMnemonicSeed;
try { try {
await this.resetRegistration(); await this.resetRegistration();
await window.setPassword(password); await window.setPassword(password);
await this.accountManager.registerSingleDevice( await this.accountManager.registerSingleDevice(
mnemonicSeed, seedToUse,
language, language,
trimName trimName
); );
trigger('openInbox'); trigger('openInbox');
} catch (e) { } catch (e) {
if (typeof e === 'string') { window.pushToast({
//this.showToast(e); title: `Error: ${e.message || 'Something went wrong'}`,
type: 'error',
id: 'registrationError',
});
let exmsg = '';
if (e.message) {
exmsg += e.message;
}
if (e.stack) {
exmsg += ` | stack: + ${e.stack}`;
} }
//this.log(e); window.log.warn('exception during registration:', exmsg);
} }
} }
@ -804,8 +830,12 @@ export class RegistrationTabs extends React.Component<{}, State> {
} }
private async registerSecondaryDevice() { private async registerSecondaryDevice() {
window.log.warn('starting registerSecondaryDevice');
// tslint:disable-next-line: no-backbone-get-set-outside-model // tslint:disable-next-line: no-backbone-get-set-outside-model
if (window.textsecure.storage.get('secondaryDeviceStatus') === 'ongoing') { if (window.textsecure.storage.get('secondaryDeviceStatus') === 'ongoing') {
window.log.warn('registering secondary device already ongoing');
return; return;
} }
this.setState({ this.setState({

@ -48,7 +48,7 @@ export class SessionConfirm extends React.Component<Props> {
const messageSubText = messageSub const messageSubText = messageSub
? 'session-confirm-main-message' ? 'session-confirm-main-message'
: 'text-subtle'; : 'subtle';
return ( return (
<SessionModal <SessionModal
@ -63,7 +63,7 @@ export class SessionConfirm extends React.Component<Props> {
<div className="session-modal__centered"> <div className="session-modal__centered">
<span className={messageSubText}>{message}</span> <span className={messageSubText}>{message}</span>
{messageSub && ( {messageSub && (
<span className="session-confirm-sub-message text-subtle"> <span className="session-confirm-sub-message subtle">
{messageSub} {messageSub}
</span> </span>
)} )}

@ -240,7 +240,7 @@ export class SessionGroupSettings extends React.Component<Props, any> {
{showMemberCount && ( {showMemberCount && (
<> <>
<div className="spacer-lg" /> <div className="spacer-lg" />
<div role="button" className="text-subtle"> <div role="button" className="subtle">
{window.i18n('members', memberCount)} {window.i18n('members', memberCount)}
</div> </div>
<div className="spacer-lg" /> <div className="spacer-lg" />

@ -20,6 +20,9 @@ interface State {
} }
export class SessionPasswordModal extends React.Component<Props, State> { export class SessionPasswordModal extends React.Component<Props, State> {
private readonly passwordInput: React.RefObject<HTMLInputElement>;
private readonly passwordInputConfirm: React.RefObject<HTMLInputElement>;
constructor(props: any) { constructor(props: any) {
super(props); super(props);
@ -34,10 +37,19 @@ export class SessionPasswordModal extends React.Component<Props, State> {
this.onKeyUp = this.onKeyUp.bind(this); this.onKeyUp = this.onKeyUp.bind(this);
this.onPaste = this.onPaste.bind(this); this.onPaste = this.onPaste.bind(this);
this.passwordInput = React.createRef();
this.passwordInputConfirm = React.createRef();
} }
public componentDidMount() { public componentDidMount() {
setTimeout(() => $('#password-modal-input').focus(), 100); setTimeout(() => {
if (!this.passwordInput.current) {
return;
}
this.passwordInput.current.focus();
}, 100);
} }
public render() { public render() {
@ -64,6 +76,7 @@ export class SessionPasswordModal extends React.Component<Props, State> {
<input <input
type="password" type="password"
id="password-modal-input" id="password-modal-input"
ref={this.passwordInput}
placeholder={placeholders[0]} placeholder={placeholders[0]}
onKeyUp={this.onKeyUp} onKeyUp={this.onKeyUp}
maxLength={window.CONSTANTS.MAX_PASSWORD_LENGTH} maxLength={window.CONSTANTS.MAX_PASSWORD_LENGTH}
@ -73,6 +86,7 @@ export class SessionPasswordModal extends React.Component<Props, State> {
<input <input
type="password" type="password"
id="password-modal-input-confirm" id="password-modal-input-confirm"
ref={this.passwordInputConfirm}
placeholder={placeholders[1]} placeholder={placeholders[1]}
onKeyUp={this.onKeyUp} onKeyUp={this.onKeyUp}
maxLength={window.CONSTANTS.MAX_PASSWORD_LENGTH} maxLength={window.CONSTANTS.MAX_PASSWORD_LENGTH}
@ -126,16 +140,21 @@ export class SessionPasswordModal extends React.Component<Props, State> {
} }
private async setPassword(onSuccess: any) { private async setPassword(onSuccess: any) {
const enteredPassword = String($('#password-modal-input').val()); if (!this.passwordInput.current || !this.passwordInputConfirm.current) {
return;
}
// Trim leading / trailing whitespace for UX
const enteredPassword = String(this.passwordInput.current.value).trim();
const enteredPasswordConfirm = String( const enteredPasswordConfirm = String(
$('#password-modal-input-confirm').val() this.passwordInputConfirm.current.value
); ).trim();
if (enteredPassword.length === 0 || enteredPasswordConfirm.length === 0) { if (enteredPassword.length === 0 || enteredPasswordConfirm.length === 0) {
return; return;
} }
// Check passwords enntered // Check passwords entered
if ( if (
enteredPassword.length === 0 || enteredPassword.length === 0 ||
(this.props.action === PasswordAction.Change && (this.props.action === PasswordAction.Change &&

@ -15,6 +15,8 @@ interface State {
} }
export class SessionPasswordPrompt extends React.PureComponent<{}, State> { export class SessionPasswordPrompt extends React.PureComponent<{}, State> {
private readonly inputRef: React.RefObject<HTMLInputElement>;
constructor(props: any) { constructor(props: any) {
super(props); super(props);
@ -29,10 +31,12 @@ export class SessionPasswordPrompt extends React.PureComponent<{}, State> {
this.initLogin = this.initLogin.bind(this); this.initLogin = this.initLogin.bind(this);
this.initClearDataView = this.initClearDataView.bind(this); this.initClearDataView = this.initClearDataView.bind(this);
this.inputRef = React.createRef();
} }
public componentDidMount() { public componentDidMount() {
setTimeout(() => $('#password-prompt-input').focus(), 100); (this.inputRef.current as HTMLInputElement).focus();
} }
public render() { public render() {
@ -65,6 +69,7 @@ export class SessionPasswordPrompt extends React.PureComponent<{}, State> {
onKeyUp={this.onKeyUp} onKeyUp={this.onKeyUp}
maxLength={window.CONSTANTS.MAX_PASSWORD_LENGTH} maxLength={window.CONSTANTS.MAX_PASSWORD_LENGTH}
onPaste={this.onPaste} onPaste={this.onPaste}
ref={this.inputRef}
/> />
); );
const infoIcon = this.state.clearDataView ? ( const infoIcon = this.state.clearDataView ? (
@ -137,15 +142,15 @@ export class SessionPasswordPrompt extends React.PureComponent<{}, State> {
}); });
} }
// Prevent pating into input // Prevent pasting into input
return false; return false;
} }
public async onLogin(passPhrase: string) { public async onLogin(passPhrase: string) {
const trimmed = passPhrase ? passPhrase.trim() : passPhrase; const passPhraseTrimmed = passPhrase.trim();
try { try {
await window.onLogin(trimmed); await window.onLogin(passPhraseTrimmed);
} catch (error) { } catch (error) {
// Increment the error counter and show the button if necessary // Increment the error counter and show the button if necessary
this.setState({ this.setState({
@ -157,7 +162,9 @@ export class SessionPasswordPrompt extends React.PureComponent<{}, State> {
} }
private async initLogin() { private async initLogin() {
const passPhrase = String($('#password-prompt-input').val()); const passPhrase = String(
(this.inputRef.current as HTMLInputElement).value
);
await this.onLogin(passPhrase); await this.onLogin(passPhrase);
} }

@ -29,7 +29,7 @@ export class SessionQRModal extends React.Component<Props> {
> >
<div className="spacer-sm" /> <div className="spacer-sm" />
<div className="qr-dialog__description text-subtle"> <div className="qr-dialog__description subtle">
<SessionHtmlRenderer html={window.i18n('QRCodeDescription')} /> <SessionHtmlRenderer html={window.i18n('QRCodeDescription')} />
</div> </div>
<div className="spacer-lg" /> <div className="spacer-lg" />

@ -121,7 +121,7 @@ export class SessionSeedModal extends React.Component<Props, State> {
<p className="session-modal__description"> <p className="session-modal__description">
{i18n('seedSavePromptMain')} {i18n('seedSavePromptMain')}
<br /> <br />
<span className="text-subtle">{i18n('seedSavePromptAlt')}</span> <span className="subtle">{i18n('seedSavePromptAlt')}</span>
</p> </p>
<div className="spacer-xs" /> <div className="spacer-xs" />

@ -6065,6 +6065,11 @@ lodash-es@^4.2.1:
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78" resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78"
integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ== integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==
lodash-es@^4.2.1:
version "4.17.15"
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78"
integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==
lodash.assign@^4.2.0: lodash.assign@^4.2.0:
version "4.2.0" version "4.2.0"
resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7"
@ -8746,8 +8751,8 @@ redent@^1.0.0:
resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde"
integrity sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94= integrity sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=
dependencies: dependencies:
indent-string "^2.1.0" loose-envify "^1.4.0"
strip-indent "^1.0.1" symbol-observable "^1.2.0"
reduce-css-calc@^1.2.6: reduce-css-calc@^1.2.6:
version "1.3.0" version "1.3.0"
@ -11213,7 +11218,7 @@ write-file-atomic@^1.1.4:
dependencies: dependencies:
graceful-fs "^4.1.11" graceful-fs "^4.1.11"
imurmurhash "^0.1.4" imurmurhash "^0.1.4"
slide "^1.1.5" signal-exit "^3.0.2"
write-file-atomic@^3.0.0: write-file-atomic@^3.0.0:
version "3.0.3" version "3.0.3"

Loading…
Cancel
Save