Implement e2e export

pull/21833/head
Richard van der Hoff 2017-01-20 15:12:50 +00:00
parent 86276450f6
commit e23deac1bb
4 changed files with 156 additions and 59 deletions

View File

@ -47,10 +47,12 @@
"browser-encrypt-attachment": "^0.3.0",
"browser-request": "^0.3.3",
"classnames": "^2.1.2",
"commonmark": "^0.27.0",
"draft-js": "^0.8.1",
"draft-js-export-html": "^0.5.0",
"draft-js-export-markdown": "^0.2.0",
"emojione": "2.2.3",
"file-saver": "^1.3.3",
"filesize": "^3.1.2",
"flux": "^2.0.3",
"fuse.js": "^2.2.0",
@ -59,7 +61,6 @@
"isomorphic-fetch": "^2.2.1",
"linkifyjs": "^2.1.3",
"lodash": "^4.13.1",
"commonmark": "^0.27.0",
"matrix-js-sdk": "matrix-org/matrix-js-sdk#develop",
"optimist": "^0.6.1",
"q": "^1.4.1",

View File

@ -14,34 +14,102 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import FileSaver from 'file-saver';
import React from 'react';
import * as Matrix from 'matrix-js-sdk';
import * as MegolmExportEncryption from '../../../utils/MegolmExportEncryption';
import sdk from '../../../index';
import * as MegolmExportEncryption from '../../../utils/MegolmExportEncryption';
const PHASE_EDIT = 1;
const PHASE_EXPORTING = 2;
export default React.createClass({
displayName: 'ExportE2eKeysDialog',
propTypes: {
matrixClient: React.PropTypes.instanceOf(Matrix.MatrixClient).isRequired,
onFinished: React.PropTypes.func.isRequired,
},
getInitialState: function() {
return {
collectedPassword: false,
phase: PHASE_EDIT,
errStr: null,
};
},
componentWillMount: function() {
this._unmounted = false;
},
componentWillUnmount: function() {
this._unmounted = true;
},
_onPassphraseFormSubmit: function(ev) {
ev.preventDefault();
console.log(this.refs.passphrase1.value);
const passphrase = this.refs.passphrase1.value;
if (passphrase !== this.refs.passphrase2.value) {
this.setState({errStr: 'Passphrases must match'});
return false;
}
if (!passphrase) {
this.setState({errStr: 'Passphrase must not be empty'});
return false;
}
this._startExport(passphrase);
return false;
},
_startExport: function(passphrase) {
// extra Promise.resolve() to turn synchronous exceptions into
// asynchronous ones.
Promise.resolve().then(() => {
return this.props.matrixClient.exportRoomKeys();
}).then((k) => {
return MegolmExportEncryption.encryptMegolmKeyFile(
JSON.stringify(k), passphrase
);
}).then((f) => {
const blob = new Blob([f], {
type: 'text/plain;charset=us-ascii',
});
FileSaver.saveAs(blob, 'riot-keys.txt');
this.props.onFinished(true);
}).catch((e) => {
if (this._unmounted) {
return;
}
this.setState({
errStr: e.message,
phase: PHASE_EDIT,
});
});
this.setState({
errStr: null,
phase: PHASE_EXPORTING,
});
},
render: function() {
let content;
if (!this.state.collectedPassword) {
content = (
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const AccessibleButton = sdk.getComponent('views.elements.AccessibleButton');
const disableForm = (this.state.phase === PHASE_EXPORTING);
return (
<BaseDialog className='mx_exportE2eKeysDialog'
onFinished={this.props.onFinished}
title="Export room keys"
>
<form onSubmit={this._onPassphraseFormSubmit}>
<div className="mx_Dialog_content">
<p>
This process will allow you to export the keys for messages
This process allows you to export the keys for messages
you have received in encrypted rooms to a local file. You
will then be able to import the file into another Matrix
client in the future, so that client will also be able to
@ -55,30 +123,49 @@ export default React.createClass({
data. It will only be possible to import the data by using the
same passphrase.
</p>
<form onSubmit={this._onPassphraseFormSubmit}>
<div className="mx_TextInputDialog_label">
<label htmlFor="passphrase1">Enter passphrase</label>
<div className='error'>
{this.state.errStr}
</div>
<div>
<input ref="passphrase1" id="passphrase1"
className="mx_TextInputDialog_input"
autoFocus={true} size="64" type="password"/>
<div className='mx_E2eKeysDialog_inputTable'>
<div className='mx_E2eKeysDialog_inputRow'>
<div className='mx_E2eKeysDialog_inputLabel'>
<label htmlFor='passphrase1'>
Enter passphrase
</label>
</div>
<div className="mx_Dialog_buttons">
<input className="mx_Dialog_primary" type="submit" value="Export" />
<div className='mx_E2eKeysDialog_inputCell'>
<input ref='passphrase1' id='passphrase1'
autoFocus={true} size='64' type='password'
disabled={disableForm}
/>
</div>
</div>
<div className='mx_E2eKeysDialog_inputRow'>
<div className='mx_E2eKeysDialog_inputLabel'>
<label htmlFor='passphrase2'>
Confirm passphrase
</label>
</div>
<div className='mx_E2eKeysDialog_inputCell'>
<input ref='passphrase2' id='passphrase2'
size='64' type='password'
disabled={disableForm}
/>
</div>
</div>
</div>
</div>
<div className='mx_Dialog_buttons'>
<input className='mx_Dialog_primary' type='submit' value='Export'
disabled={disableForm}
/>
<AccessibleButton element='button' onClick={this.props.onFinished}
disabled={disableForm}>
Cancel
</AccessibleButton>
</div>
</form>
</div>
);
}
return (
<div className="mx_exportE2eKeysDialog">
<div className="mx_Dialog_title">
Export room keys
</div>
{content}
</div>
</BaseDialog>
);
},
});

View File

@ -393,6 +393,16 @@ module.exports = React.createClass({
}).done();
},
_onExportE2eKeysClicked: function() {
Modal.createDialogAsync(
(cb) => {
require(['../../async-components/views/dialogs/ExportE2eKeysDialog'], cb);
}, {
matrixClient: MatrixClientPeg.get(),
}
);
},
_renderUserInterfaceSettings: function() {
var client = MatrixClientPeg.get();
@ -463,6 +473,16 @@ module.exports = React.createClass({
const deviceId = client.deviceId;
const identityKey = client.getDeviceEd25519Key() || "<not supported>";
let exportButton = null;
if (client.isCryptoEnabled) {
exportButton = (
<AccessibleButton className="mx_UserSettings_button"
onClick={this._onExportE2eKeysClicked}>
Export E2E room keys
</AccessibleButton>
);
}
return (
<div>
<h3>Cryptography</h3>
@ -471,6 +491,7 @@ module.exports = React.createClass({
<li><label>Device ID:</label> <span><code>{deviceId}</code></span></li>
<li><label>Device key:</label> <span><code><b>{identityKey}</b></code></span></li>
</ul>
{exportButton}
</div>
</div>
);

View File

@ -29,20 +29,7 @@ module.exports.getComponent = function(componentName) {
};
/* hacky functions for megolm import/export until we give it a UI */
import * as MegolmExportEncryption from './utils/MegolmExportEncryption';
import MatrixClientPeg from './MatrixClientPeg';
window.exportKeys = function(password) {
return MatrixClientPeg.get().exportRoomKeys().then((k) => {
return MegolmExportEncryption.encryptMegolmKeyFile(
JSON.stringify(k), password
);
}).then((f) => {
console.log(new TextDecoder().decode(new Uint8Array(f)));
}).done();
};
/*
window.importKeys = function(password, data) {
const arrayBuffer = new TextEncoder().encode(data).buffer;
return MegolmExportEncryption.decryptMegolmKeyFile(
@ -52,3 +39,4 @@ window.importKeys = function(password, data) {
return MatrixClientPeg.get().importRoomKeys(k);
});
};
*/