Skip to content

Commit

Permalink
e2ee: add UI elements
Browse files Browse the repository at this point in the history
* Add dialog to set the E2EE key
* Use the Redux action / middleware to update the key even when set through the
  hash parameter
* Cleanup URL after processing the key so it's not recorded in browser history
  • Loading branch information
saghul committed Apr 16, 2020
1 parent 0077ee2 commit cb6fbb0
Show file tree
Hide file tree
Showing 15 changed files with 365 additions and 9 deletions.
15 changes: 7 additions & 8 deletions conference.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ import {
import { getJitsiMeetGlobalNS } from './react/features/base/util';
import { showDesktopPicker } from './react/features/desktop-picker';
import { appendSuffix } from './react/features/display-name';
import { setE2EEKey } from './react/features/e2ee';
import {
maybeOpenFeedbackDialog,
submitFeedback
Expand Down Expand Up @@ -470,11 +471,6 @@ export default {
*/
localVideo: null,

/**
* The key used for End-To-End Encryption.
*/
e2eeKey: undefined,

/**
* Creates local media tracks and connects to a room. Will show error
* dialogs in case accessing the local microphone and/or camera failed. Will
Expand Down Expand Up @@ -1202,11 +1198,14 @@ export default {
items[key] = param[1];
}

this.e2eeKey = items.e2eekey;
if (typeof items.e2eekey !== undefined) {
APP.store.dispatch(setE2EEKey(items.e2eekey));

logger.debug(`New E2EE key: ${this.e2eeKey}`);
// Clean URL in browser history.
const cleanUrl = window.location.href.split('#')[0];

this._room.setE2EEKey(this.e2eeKey);
history.replaceState(history.state, document.title, cleanUrl);
}
},

/**
Expand Down
3 changes: 2 additions & 1 deletion interface_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ var interfaceConfig = {
'fodeviceselection', 'hangup', 'profile', 'info', 'chat', 'recording',
'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand',
'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
'tileview', 'videobackgroundblur', 'download', 'help', 'mute-everyone'
'tileview', 'videobackgroundblur', 'download', 'help', 'mute-everyone',
'e2ee'
],

SETTINGS_SECTIONS: [ 'devices', 'language', 'moderator', 'profile', 'calendar' ],
Expand Down
5 changes: 5 additions & 0 deletions lang/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@
"dismiss": "Dismiss",
"displayNameRequired": "Hi! What’s your name?",
"done": "Done",
"e2eeDescription": "<p>End-to-End Encryption is currently <strong>EXPERIMENTAL</strong>. Please see <a href='https://jitsi.org/blog/e2ee/' target='_blank'>this post</a> for details.</p><br/><p>Please keep in mind that turning on end-to-end encryption will effectively disable server-side provided services such as: recording, live streaming and phone participation. Also keep in mind that the meeting will only work for people joining from browsers with support for insertable streams.</p>",
"e2eeLabel": "Key",
"e2eeTitle": "End-to-End Encryption",
"enterDisplayName": "Please enter your name here",
"error": "Error",
"externalInstallationMsg": "You need to install our desktop sharing extension.",
Expand Down Expand Up @@ -595,6 +598,7 @@
"chat": "Toggle chat window",
"document": "Toggle shared document",
"download": "Download our apps",
"e2ee": "End-to-End Encryption",
"feedback": "Leave feedback",
"fullScreen": "Toggle full screen",
"hangup": "Leave the call",
Expand Down Expand Up @@ -638,6 +642,7 @@
"documentClose": "Close shared document",
"documentOpen": "Open shared document",
"download": "Download our apps",
"e2ee": "End-to-End Encryption",
"enterFullScreen": "View full screen",
"enterTileView": "Enter tile view",
"exitFullScreen": "Exit full screen",
Expand Down
14 changes: 14 additions & 0 deletions react/features/analytics/AnalyticsEvents.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,20 @@ export function createDeviceChangedEvent(mediaType, deviceType) {
};
}

/**
* Creates an event indicating that an action related to E2EE occurred.
*
* @param {string} action - The action which occurred.
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export function createE2EEEvent(action) {
return {
action,
actionSubject: 'e2ee'
};
}

/**
* Creates an event which specifies that the feedback dialog has been opened.
*
Expand Down
5 changes: 5 additions & 0 deletions react/features/base/conference/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { isRoomValid } from './functions';

const DEFAULT_STATE = {
conference: undefined,
e2eeSupported: undefined,
joining: undefined,
leaving: undefined,
locked: undefined,
Expand Down Expand Up @@ -175,6 +176,7 @@ function _conferenceFailed(state, { conference, error }) {
return assign(state, {
authRequired,
conference: undefined,
e2eeSupported: undefined,
error,
joining: undefined,
leaving: undefined,
Expand Down Expand Up @@ -226,6 +228,9 @@ function _conferenceJoined(state, { conference }) {
* @type {JitsiConference}
*/
conference,

e2eeSupported: conference.isE2EESupported(),

joining: undefined,
leaving: undefined,

Expand Down
8 changes: 8 additions & 0 deletions react/features/e2ee/actionTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* The type of the action which signals the E2EE key has changed.
*
* {
* type: SET_E2EE_KEY
* }
*/
export const SET_E2EE_KEY = 'SET_E2EE_KEY';
16 changes: 16 additions & 0 deletions react/features/e2ee/actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// @flow

import { SET_E2EE_KEY } from './actionTypes';

/**
* Dispatches an action to set the E2EE key.
*
* @param {string|undefined} key - The new key to be used for E2EE.
* @returns {Object}
*/
export function setE2EEKey(key: ?string) {
return {
type: SET_E2EE_KEY,
key
};
}
76 changes: 76 additions & 0 deletions react/features/e2ee/components/E2EEButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// @flow

import React from 'react';

import { createE2EEEvent, sendAnalytics } from '../../analytics';
import { openDialog } from '../../base/dialog';
import { translate } from '../../base/i18n';
import { IconRoomUnlock } from '../../base/icons';
import { connect } from '../../base/redux';
import { AbstractButton, BetaTag } from '../../base/toolbox';
import type { AbstractButtonProps } from '../../base/toolbox';

import E2EEDialog from './E2EEDialog';


type Props = AbstractButtonProps & {

/**
* The redux {@code dispatch} function.
*/
dispatch: Function

};

/**
* Button that open a dialog to set the E2EE key.
*/
class E2EEButton extends AbstractButton<Props, *> {
accessibilityLabel = 'toolbar.accessibilityLabel.e2ee';
icon = IconRoomUnlock;
label = 'toolbar.e2ee';
tooltip = 'toolbar.e2ee';

/**
* Helper function to be implemented by subclasses, which returns
* a React Element to display (a beta tag) at the end of the button.
*
* @override
* @protected
* @returns {ReactElement}
*/
_getElementAfter() {
return <BetaTag />;
}

/**
* Handles clicking / pressing the button, and opens the E2EE dialog.
*
* @protected
* @returns {void}
*/
_handleClick() {
sendAnalytics(createE2EEEvent('dialog.open'));
this.props.dispatch(openDialog(E2EEDialog));
}
}

/**
* Maps (parts of) the redux state to the associated props for this component.
*
* @param {Object} state - The Redux state.
* @param {Props} ownProps - The own props of the Component.
* @private
* @returns {Props}
*/
export function mapStateToProps(state: Object, ownProps: Props) {
const { e2eeSupported } = state['features/base/conference'];
const { visible = Boolean(e2eeSupported) } = ownProps;

return {
visible
};
}


export default translate(connect(mapStateToProps)(E2EEButton));
142 changes: 142 additions & 0 deletions react/features/e2ee/components/E2EEDialog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/* @flow */

import React, { Component } from 'react';
import type { Dispatch } from 'redux';
import { FieldTextStateless as TextField } from '@atlaskit/field-text';

import { createE2EEEvent, sendAnalytics } from '../../analytics';
import { Dialog } from '../../base/dialog';
import { translate, translateToHTML } from '../../base/i18n';
import { connect } from '../../base/redux';

import { setE2EEKey } from '../actions';


type Props = {

/**
* The current E2EE key.
*/
_key: string,

/**
* The redux {@code dispatch} function.
*/
dispatch: Dispatch<any>,

/**
* Invoked to obtain translated strings.
*/
t: Function
};

type State = {

/**
* The current E2EE key.
*/
key: string
};

/**
* Implements a React {@code Component} for displaying a dialog with a field
* for setting the E2EE key.
*
* @extends Component
*/
class E2EEDialog extends Component<Props, State> {
/**
* Initializes a new {@code E2EEDialog } instance.
*
* @param {Object} props - The read-only properties with which the new
* instance is to be initialized.
*/
constructor(props: Props) {
super(props);

this.state = {
key: this.props._key
};

// Bind event handlers so they are only bound once for every instance.
this._onKeyChange = this._onKeyChange.bind(this);
this._onSubmit = this._onSubmit.bind(this);
}

/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const { t } = this.props;

return (
<Dialog
isModal = { false }
onSubmit = { this._onSubmit }
titleKey = 'dialog.e2eeTitle'
width = 'small'>
<div className = 'e2ee-destription'>
{ translateToHTML(t, 'dialog.e2eeDescription') }
</div>
<TextField
autoFocus = { true }
compact = { true }
label = { t('dialog.e2eeLabel') }
name = 'e2eeKey'
onChange = { this._onKeyChange }
shouldFitContainer = { true }
type = 'password'
value = { this.state.key } />
</Dialog>);
}

_onKeyChange: (Object) => void;

/**
* Updates the entered key.
*
* @param {Object} event - The DOM event triggered from the entered value having changed.
* @private
* @returns {void}
*/
_onKeyChange(event) {
this.setState({ key: event.target.value.trim() });
}

_onSubmit: () => boolean;

/**
* Dispatches an action to update the E2EE key.
*
* @private
* @returns {boolean}
*/
_onSubmit() {
const { key } = this.state;

sendAnalytics(createE2EEEvent(`key.${key ? 'set' : 'unset'}`));
this.props.dispatch(setE2EEKey(key));

return true;
}
}

/**
* Maps (parts of) the Redux state to the associated props for this component.
*
* @param {Object} state - The Redux state.
* @private
* @returns {Props}
*/
function mapStateToProps(state) {
const { e2eeKey } = state['features/e2ee'];

return {
_key: e2eeKey || ''
};
}

export default translate(connect(mapStateToProps)(E2EEDialog));
2 changes: 2 additions & 0 deletions react/features/e2ee/components/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as E2EEButton } from './E2EEButton';
export { default as E2EEDialog } from './E2EEDialog';
6 changes: 6 additions & 0 deletions react/features/e2ee/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export * from './actions';
export * from './actionTypes';
export * from './components';

import './middleware';
import './reducer';
5 changes: 5 additions & 0 deletions react/features/e2ee/logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// @flow

import { getLogger } from '../base/logging/functions';

export default getLogger('features/e2ee');
Loading

0 comments on commit cb6fbb0

Please sign in to comment.