Skip to content
This repository has been archived by the owner on Dec 11, 2019. It is now read-only.

Commit

Permalink
allow browser-laptop to join an existing sync profile
Browse files Browse the repository at this point in the history
by entering the 16 code words
  • Loading branch information
diracdeltas committed Jan 19, 2017
1 parent a3dd434 commit ff8a691
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 43 deletions.
12 changes: 6 additions & 6 deletions app/extensions/brave/locales/en-US/preferences.properties
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ paymentsSidebarText2=All transaction IP addresses are anonymized with technology
paymentsSidebarText3=Brave Bitcoin Wallets are provided through a partnership with:
paymentsSidebarText4=Your contributions in the form of credit cards and bank cards are handled by:
syncTitle=Brave Sync
syncTitleMessage=Brave Sync allows you to automatically share encrypted browsing data and preferences between devices running Brave.
syncTitleMessage=Sync encrypted browser data between your devices securely and privately using Brave Sync.
syncEnable=Sync this device
syncData=Sync Data
syncDataMessage=Sync the following data from this device:
Expand All @@ -36,18 +36,18 @@ syncHistory=Browsing history
syncSiteSettings=Saved site settings
syncNewDevice=Sync a new device...
syncStart=I am new to sync
syncAdd=I have an existing synced device
syncAdd=I have an existing sync code
syncRestartNeeded=Please restart to finish sync setup!
syncNewDevice1=Open Brave on your new device and go to Preferences > Sync > 'I have an existing synced device'.
syncNewDevice2=If it asks you to scan a QR code, click the button below and point your camera at the QR code.
syncShowQR=Show secret QR code. (Do not share!)
syncHideQR=Hide QR code
syncNewDevice3=If asks you to enter a passphrase, type in the secret words below.
syncShowPassphrase=Show secret words. (Do not share!)
syncHidePassphrase=Hide passphrase
syncNewDevice4=You're done! Repeat these steps to add more devices to sync.
syncNewDevice3=If asks you to enter code words, type in the words below.
syncShowPassphrase=Show secret code words. (Do not share!)
syncHidePassphrase=Hide code words
syncDeviceName=Enter an optional name for this device:
syncCreate=Set up sync
syncEnterPassphrase=Enter your sync code words:
accountBalance=account balance
accountBalanceConnectionError=Please check your Internet connection.
accountBalanceLoading=loading…
Expand Down
26 changes: 16 additions & 10 deletions app/sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -220,33 +220,39 @@ module.exports.onSyncReady = (isFirstRun, e) => {
}

module.exports.init = function (initialState) {
if (getSetting(settings.SYNC_ENABLED) !== true) {
return
}
ipcMain.on(messages.GET_INIT_DATA, (e) => {
const seed = initialState.seed || null
deviceId = initialState.deviceId || null
e.sender.send(messages.GOT_INIT_DATA, seed, deviceId, config)
})
// SAVE_INIT_DATA is sent by about:preferences before sync is enabled
// when restoring from an existing seed
ipcMain.on(messages.SAVE_INIT_DATA, (e, seed, newDeviceId) => {
if (!deviceId && newDeviceId) {
deviceId = Array.from(newDeviceId)
}
if (!seed && newDeviceId) {
appActions.saveSyncInitData(null, new Immutable.List(newDeviceId), null)
return
}
try {
let chunks = []
qr.image(Buffer.from(seed).toString('hex')).on('data', (chunk) => {
chunks.push(chunk)
}).on('end', () => {
let seedQr = 'data:image/png;base64,' + Buffer.concat(chunks).toString('base64')
appActions.saveSyncInitData(new Immutable.List(seed),
new Immutable.List(newDeviceId), null, seedQr)
newDeviceId ? new Immutable.List(newDeviceId) : null, null, seedQr)
})
} catch (ex) {
console.log('qr image error: ' + ex.toString())
appActions.saveSyncInitData(new Immutable.List(seed),
new Immutable.List(newDeviceId))
newDeviceId ? new Immutable.List(newDeviceId) : null)
}
})
if (getSetting(settings.SYNC_ENABLED) !== true) {
return
}
ipcMain.on(messages.GET_INIT_DATA, (e) => {
const seed = initialState.seed || null
deviceId = initialState.deviceId || null
e.sender.send(messages.GOT_INIT_DATA, seed, deviceId, config)
})
ipcMain.on(messages.SYNC_READY, module.exports.onSyncReady.bind(null,
!initialState.seed && !initialState.deviceId))
ipcMain.on(messages.SYNC_DEBUG, (e, msg) => {
Expand Down
8 changes: 4 additions & 4 deletions docs/appActions.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ Changes an application level setting



### changeSiteSetting(hostPattern, key, value, temp, skipSync)
### changeSiteSetting(hostPattern, key, value, temp, skipSync)

Change a hostPattern's config

Expand All @@ -306,11 +306,11 @@ Change a hostPattern's config
**temp**: `boolean`, Whether to change temporary or persistent
settings. defaults to false (persistent).

**skipSync**: `boolean`, Set true if a site isn't eligible for Sync (e.g. if this update was triggered by Sync)
**skipSync**: `boolean`, Set true if a site isn't eligible for Sync (e.g. if addSite was triggered by Sync)



### removeSiteSetting(hostPattern, key, temp, skipSync)
### removeSiteSetting(hostPattern, key, temp, skipSync)

Removes a site setting

Expand All @@ -323,7 +323,7 @@ Removes a site setting
**temp**: `boolean`, Whether to change temporary or persistent
settings. defaults to false (persistent).

**skipSync**: `boolean`, Set true if a site isn't eligible for Sync (e.g. if this update was triggered by Sync)
**skipSync**: `boolean`, Set true if a site isn't eligible for Sync (e.g. if addSite was triggered by Sync)



Expand Down
8 changes: 8 additions & 0 deletions js/about/aboutActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,14 @@ const aboutActions = {
})
},

/**
* Dispatches a message when sync init data needs to be saved
* @param {Array.<number>|null} seed
*/
saveSyncInitData: function (seed) {
ipc.send(messages.SAVE_INIT_DATA, seed)
},

/**
* Loads a URL in a new frame in a safe way.
* It is important that it is not a simple anchor because it should not
Expand Down
49 changes: 40 additions & 9 deletions js/about/preferences.js
Original file line number Diff line number Diff line change
Expand Up @@ -1437,7 +1437,7 @@ class SyncTab extends ImmutableComponent {
return <div><div className='settingsList' id='syncEnableSwitch'>
<SettingCheckbox dataL10nId='syncEnable' prefKey={settings.SYNC_ENABLED} settings={this.props.settings} onChangeSetting={this.props.onChangeSetting} />
</div>
<Button l10nId='syncNewDevice' className='whiteButton' onClick={this.props.showOverlay.bind(this, 'syncNewDevice')} />
<Button l10nId='syncNewDevice' className='whiteButton syncNewDeviceButton' onClick={this.props.showOverlay.bind(this, 'syncNewDevice')} />
</div>
}

Expand All @@ -1450,7 +1450,7 @@ class SyncTab extends ImmutableComponent {
<div><Button l10nId='syncHideQR' className='whiteButton syncToggleButton' onClick={this.props.hideQR} /></div>
<img id='syncQR' title='Brave sync QR code' src={this.props.syncData.get('seedQr')} />
</div>
: <Button l10nId='syncShowQR' className='whiteButton syncToggleButton' onClick={this.props.showQR} />
: <Button l10nId='syncShowQR' className='whiteButton syncToggleButton' onClick={this.props.showQR} />
}

get passphraseContent () {
Expand Down Expand Up @@ -1482,23 +1482,35 @@ class SyncTab extends ImmutableComponent {
{this.qrcodeContent}
<li data-l10n-id='syncNewDevice3' />
{this.passphraseContent}
<li data-l10n-id='syncNewDevice4' />
</ol>
</div>
</div>
}

get addOverlayContent () {
return <div className='syncOverlay' data-l10n-id='comingSoon' />
}

get startOverlayContent () {
return <div className='syncOverlay'>
get deviceNameInputContent () {
return <div>
<span data-l10n-id='syncDeviceName' />
<input spellCheck='false'
ref={(node) => { this.deviceNameInput = node }}
className='form-control'
placeholder={getSetting(settings.SYNC_DEVICE_NAME, this.props.settings)} />
</div>
}

get addOverlayContent () {
return <div className='syncOverlay'>
<p data-l10n-id='syncEnterPassphrase' />
<textarea spellCheck='false'
ref={(node) => { this.passphraseInput = node }}
className='form-control' />
<div>{this.deviceNameInputContent}</div>
<Button l10nId='syncCreate' className='primaryButton' onClick={this.restoreSyncProfile.bind(this)} />
</div>
}

get startOverlayContent () {
return <div className='syncOverlay'>
{this.deviceNameInputContent}
<div>
<Button l10nId='syncCreate' className='primaryButton' onClick={this.setupSyncProfile.bind(this)} />
</div>
Expand All @@ -1513,6 +1525,25 @@ class SyncTab extends ImmutableComponent {
this.props.hideOverlay('syncStart')
}

restoreSyncProfile () {
if (this.passphraseInput.value) {
let text = this.passphraseInput.value.toLowerCase().replace(/,/g, ' ').replace(/\s+/g, ' ').trim()
let inputCode = ''
try {
inputCode = window.niceware.passphraseToBytes(text.split(' '))
} catch (e) {
console.log('Could not convert niceware passphrase', e)
}
if (inputCode && inputCode.length === 32) {
// QR code and device ID are set after sync restarts
aboutActions.saveSyncInitData(Array.from(inputCode))
this.setupSyncProfile()
return
}
}
window.alert('Invalid input code; please try again or create a new profile.')
}

render () {
return <div id='syncContainer'>
{
Expand Down
1 change: 1 addition & 0 deletions js/constants/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ const messages = {
RENDER_URL_TO_PDF: _,
// Sync
SYNC_UPDATED: _,
SAVE_INIT_DATA: _,
// Torrent
TORRENT_MESSAGE: _
}
Expand Down
12 changes: 11 additions & 1 deletion less/about/preferences.less
Original file line number Diff line number Diff line change
Expand Up @@ -1553,9 +1553,15 @@ table.sortableTable {
margin-left: 20px;
display: inline;
}
textarea {
width: 80%;
height: 100px;
font-size: 18px;
font-family: monospace;
}
padding-left: 50px;
padding-bottom: 50px;
div, input, span {
div, input, textarea, span {
margin-top: 20px;
}
li {
Expand All @@ -1569,6 +1575,10 @@ table.sortableTable {
margin-top: 20px;
}

.syncNewDeviceButton {
margin-right: 20px;
}

.syncWarning {
color: #ff0400;
margin-top: 20px;
Expand Down
76 changes: 67 additions & 9 deletions test/components/syncTest.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
/* global describe, it, before, beforeEach */
/* global describe, it, beforeEach */

const Brave = require('../lib/brave')
const Immutable = require('immutable')
const {urlInput, syncTab, syncSwitch} = require('../lib/selectors')

const prefsUrl = 'about:preferences'
const startButton = '[data-l10n-id="syncStart"]'
const addButton = '[data-l10n-id="syncAdd"]'
const createButton = '[data-l10n-id="syncCreate"]'
const newDeviceButton = '[data-l10n-id="syncNewDevice"]'

function toHex (byteArray) {
let str = ''
for (var i = 0; i < byteArray.length; i++) {
let char = byteArray[i].toString(16)
if (char.length === 1) {
char = '0' + char
}
str = str + char
}
return str
}

function * setup (client) {
yield client
Expand All @@ -16,8 +31,8 @@ function * setup (client) {

describe('Sync Panel', function () {
describe('sync setup', function () {
Brave.beforeAll(this)
before(function * () {
Brave.beforeEach(this)
beforeEach(function * () {
yield setup(this.app.client)
})

Expand All @@ -30,21 +45,59 @@ describe('Sync Panel', function () {
.waitForVisible(startButton)
.click(startButton)
.waitForVisible(createButton)
.setValue('input', 'pyramid 0')
.click(createButton)
.windowByUrl(Brave.browserWindowUrl)
.waitUntil(function () {
return this.getAppState().then((val) => {
return val.value.settings['sync.enabled'] === true
return val.value.settings['sync.enabled'] === true &&
val.value.settings['sync.device-name'] === 'pyramid 0'
})
})
})

it('sync profile can be recreated', function * () {
const codewords = 'Idyllic undergrowth sheepman chez wishy undergroundeR verseman plyer a, a, a, a, a, a, a, a '
const hex = '68c2ecccc83a2080fc8beccbf55da43c00000000000000000000000000000000'
yield this.app.client
.tabByIndex(0)
.loadUrl(prefsUrl)
.waitForVisible(syncTab)
.click(syncTab)
.waitForVisible(addButton)
.click(addButton)
.setValue('textarea', codewords)
.setValue('input', 'pyramid 1')
.click(createButton)
.windowByUrl(Brave.browserWindowUrl)
.waitUntil(function () {
return this.getAppState().then((val) => {
return val.value.settings['sync.enabled'] === true &&
val.value.settings['sync.device-name'] === 'pyramid 1' &&
toHex(val.value.sync.seed) === hex &&
val.value.sync.seedQr.startsWith('data:image/png;base64,')
})
})
.tabByIndex(0)
.loadUrl(prefsUrl)
.waitForVisible(syncTab)
.click(syncTab)
.waitForExist(newDeviceButton)
.click(newDeviceButton)
.click('[data-l10n-id="syncShowPassphrase"]')
.waitUntil(function () {
return this.getText('#syncPassphrase').then((text) => {
return text === 'idyllic undergrowth sheepman chez\nwishy undergrounder verseman plyer\na a a a\na a a a'
})
})
})
})

describe('after sync setup', function () {
describe('sync post-setup', function () {
Brave.beforeEach(this)
beforeEach(function * () {
yield setup(this.app.client)
yield this.app.client.saveSyncInitData([
yield this.app.client.saveSyncInitData(Immutable.fromJS([
0,
0,
0,
Expand Down Expand Up @@ -77,7 +130,7 @@ describe('Sync Panel', function () {
0,
0,
0
])
]), Immutable.fromJS([0]), 0, 'data:image/png;base64,foo')
})

it('sync can be toggled', function * () {
Expand Down Expand Up @@ -123,8 +176,7 @@ describe('Sync Panel', function () {
})
})

it('shows sync secret words', function * () {
const newDeviceButton = '[data-l10n-id="syncNewDevice"]'
it('shows sync QR code and words', function * () {
yield this.app.client
.tabByIndex(0)
.loadUrl(prefsUrl)
Expand All @@ -140,6 +192,12 @@ describe('Sync Panel', function () {
return text === 'a a a a\na a a a\na a a a\na a a a'
})
})
.click('[data-l10n-id="syncShowQR"]')
.waitUntil(function () {
return this.getAttribute('#syncQR', 'src').then((text) => {
return text === 'data:image/png;base64,foo'
})
})
})
})
})
Loading

0 comments on commit ff8a691

Please sign in to comment.