feat: insecure room name warning

This commit is contained in:
Bettenbuk Zoltan 2020-05-18 14:07:09 +02:00 committed by Zoltan Bettenbuk
parent 9525cab60f
commit c08638da51
26 changed files with 451 additions and 140 deletions

68
css/_labels.scss Normal file
View File

@ -0,0 +1,68 @@
.large-video-labels {
display: flex;
position: absolute;
top: 30px;
right: 30px;
transition: right 0.5s;
z-index: $zindex3;
.circular-label {
align-items: center;
color: white;
display: flex;
font-weight: bold;
justify-content: center;
margin-left: 8px;
opacity: 0.8;
}
.circular-label {
background: #B8C7E0;
}
.circular-label.e2ee {
align-items: center;
background: #76CF9C;
display: flex;
justify-content: center;
}
.circular-label.file {
background: #FF5630;
}
.circular-label.local-rec {
background: #FF5630;
}
.circular-label.stream {
background: #0065FF;
}
.circular-label.insecure {
background: $defaultWarningColor;
}
.recording-label.center-message {
background: $videoStateIndicatorBackground;
bottom: 50%;
display: block;
left: 50%;
padding: 10px;
position: fixed;
transform: translate(-50%, -50%);
z-index: $centeredVideoLabelZ;
}
}
.circular-label {
background: $videoStateIndicatorBackground;
border-radius: 50%;
box-sizing: border-box;
cursor: default;
font-size: 13px;
height: $videoStateIndicatorSize;
line-height: $videoStateIndicatorSize;
text-align: center;
min-width: $videoStateIndicatorSize;
}

View File

@ -71,9 +71,6 @@ body.welcome-page {
text-align: left;
color: #253858;
height: fit-content;
border-width: $welcomePageEnterRoomInputContainerBorderWidth;
border-style: $welcomePageEnterRoomInputContainerBorderStyle;
border-image: $welcomePageEnterRoomInputContainerBorderImage;
.enter-room-title {
display: $welcomePageEnterRoomTitleDisplay;
@ -83,12 +80,26 @@ body.welcome-page {
}
.enter-room-input {
border: none;
border-width: $welcomePageEnterRoomInputContainerBorderWidth;
border-style: $welcomePageEnterRoomInputContainerBorderStyle;
border-image: $welcomePageEnterRoomInputContainerBorderImage;
display: inline-block;
width: 100%;
font-size: 14px;
}
.insecure-room-name-warning {
align-items: center;
color: $defaultWarningColor;
display: flex;
flex-direction: row;
margin-top: 5px;
svg {
fill: $defaultWarningColor
}
}
::placeholder {
color: #253858;
}

View File

@ -75,6 +75,7 @@ $flagsImagePath: "../images/";
@import 'filmstrip/tile_view_overrides';
@import 'filmstrip/vertical_filmstrip';
@import 'filmstrip/vertical_filmstrip_overrides';
@import 'labels';
@import 'unsupported-browser/main';
@import 'modals/invite/add-people';
@import 'deep-linking/main';

View File

@ -144,65 +144,3 @@
#videoResolutionLabel {
z-index: $zindex3 + 1;
}
.large-video-labels {
display: flex;
position: absolute;
top: 30px;
right: 30px;
transition: right 0.5s;
z-index: $zindex3;
.circular-label {
color: white;
font-weight: bold;
margin-left: 8px;
opacity: 0.8;
}
.circular-label {
background: #B8C7E0;
}
.circular-label.e2ee {
align-items: center;
background: #76CF9C;
display: flex;
justify-content: center;
}
.circular-label.file {
background: #FF5630;
}
.circular-label.local-rec {
background: #FF5630;
}
.circular-label.stream {
background: #0065FF;
}
.recording-label.center-message {
background: $videoStateIndicatorBackground;
bottom: 50%;
display: block;
left: 50%;
padding: 10px;
position: fixed;
transform: translate(-50%, -50%);
z-index: $centeredVideoLabelZ;
}
}
.circular-label {
background: $videoStateIndicatorBackground;
border-radius: 50%;
box-sizing: border-box;
cursor: default;
font-size: 13px;
height: $videoStateIndicatorSize;
line-height: $videoStateIndicatorSize;
text-align: center;
min-width: $videoStateIndicatorSize;
}

View File

@ -561,6 +561,9 @@
"sectionList": {
"pullToRefresh": "Pull to refresh"
},
"security": {
"insecureRoomNameWarning": "The room name is insecure. Unwanted participants may join your conference."
},
"settings": {
"calendar": {
"about": "The {{appName}} calendar integration is used to securely access your calendar so it can read upcoming events.",

5
package-lock.json generated
View File

@ -19178,6 +19178,11 @@
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
}
}
},
"zxcvbn": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/zxcvbn/-/zxcvbn-4.4.2.tgz",
"integrity": "sha1-KOwXzwl0PtyrBW3dixsGJizHPDA="
}
}
}

View File

@ -93,7 +93,8 @@
"util": "0.12.1",
"uuid": "3.1.0",
"windows-iana": "^3.1.0",
"xmldom": "0.1.27"
"xmldom": "0.1.27",
"zxcvbn": "4.4.2"
},
"devDependencies": {
"@babel/core": "7.5.5",

View File

@ -86,3 +86,4 @@ export { default as IconVideoQualitySD } from './SD.svg';
export { default as IconVolume } from './volume.svg';
export { default as IconVolumeEmpty } from './volume-empty.svg';
export { default as IconVolumeOff } from './volume-off.svg';
export { default as IconWarning } from './warning.svg';

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/></svg>

After

Width:  |  Height:  |  Size: 188 B

View File

@ -1,5 +1,29 @@
// @flow
/**
* A helper function that behaves similar to Object.assign, but only reassigns a
* property in target if it's defined in source.
*
* @param {Object} target - The target object to assign the values into.
* @param {Object} source - The source object.
* @returns {Object}
*/
export function assignIfDefined(target: Object, source: Object) {
const to = Object(target);
for (const nextKey in source) {
if (source.hasOwnProperty(nextKey)) {
const value = source[nextKey];
if (typeof value !== 'undefined') {
to[nextKey] = value;
}
}
}
return to;
}
/**
* Creates a deferred object.
*
@ -72,30 +96,6 @@ export function getJitsiMeetGlobalNS() {
return window.JitsiMeetJS.app;
}
/**
* A helper function that behaves similar to Object.assign, but only reassigns a
* property in target if it's defined in source.
*
* @param {Object} target - The target object to assign the values into.
* @param {Object} source - The source object.
* @returns {Object}
*/
export function assignIfDefined(target: Object, source: Object) {
const to = Object(target);
for (const nextKey in source) {
if (source.hasOwnProperty(nextKey)) {
const value = source[nextKey];
if (typeof value !== 'undefined') {
to[nextKey] = value;
}
}
}
return to;
}
/**
* Prints the error and reports it to the global error handler.
*

View File

@ -0,0 +1,13 @@
// @flow
import zxcvbn from 'zxcvbn';
/**
* Returns true if the room name is considered a weak (insecure) one.
*
* @param {string} roomName - The room name.
* @returns {boolean}
*/
export default function isInsecureRoomName(roomName: string = ''): boolean {
return zxcvbn(roomName).score < 3;
}

View File

@ -0,0 +1,57 @@
// @flow
import { PureComponent } from 'react';
import isInsecureRoomName from '../../base/util/isInsecureRoomName';
type Props = {
/**
* True of the label should be visible.
*/
_visible: boolean;
/**
* Function to be used to translate i18n labels.
*/
t: Function
}
/**
* Abstrsact class for the {@Code InsecureRoomNameLabel} component.
*/
export default class AbstractInsecureRoomNameLabel extends PureComponent<Props> {
/**
* Implements {@code Component#render}.
*
* @inheritdoc
*/
render() {
if (!this.props._visible) {
return null;
}
return this._render();
}
/**
* Renders the platform dependant content.
*
* @returns {ReactElement}
*/
_render: () => Object;
}
/**
* Maps part of the Redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @returns {Props}
*/
export function _mapStateToProps(state: Object): $Shape<Props> {
const { room } = state['features/base/conference'];
return {
_visible: room && isInsecureRoomName(room)
};
}

View File

@ -10,6 +10,8 @@ import { TranscribingLabel } from '../../transcribing';
import { shouldDisplayTileView } from '../../video-layout';
import { VideoQualityLabel } from '../../video-quality';
import { InsecureRoomNameLabel } from '.';
/**
* The type of the React {@code Component} props of {@link AbstractLabels}.
*/
@ -84,6 +86,18 @@ export default class AbstractLabels<P: Props, S> extends Component<P, S> {
);
}
/**
* Renders the {@code InsecureRoomNameLabel}.
*
* @protected
* @returns {React$Element}
*/
_renderInsecureRoomNameLabel() {
return (
<InsecureRoomNameLabel />
);
}
/**
* Renders the {@code VideoQualityLabel} that is platform independent.
*

View File

@ -0,0 +1,36 @@
// @flow
import { translate } from '../../../base/i18n';
import { ExpandedLabel, type Props as AbstractProps } from '../../../base/label';
import { INSECURE_ROOM_NAME_LABEL_COLOR } from './styles';
type Props = AbstractProps & {
t: Function
}
/**
* A react {@code Component} that implements an expanded label as tooltip-like
* component to explain the meaning of the {@code InsecureRoomNameExpandedLabel}.
*/
class InsecureRoomNameExpandedLabel extends ExpandedLabel<Props> {
/**
* Returns the color this expanded label should be rendered with.
*
* @returns {string}
*/
_getColor() {
return INSECURE_ROOM_NAME_LABEL_COLOR;
}
/**
* Returns the label specific text of this {@code ExpandedLabel}.
*
* @returns {string}
*/
_getLabel() {
return this.props.t('security.insecureRoomNameWarning');
}
}
export default translate(InsecureRoomNameExpandedLabel);

View File

@ -0,0 +1,31 @@
// @flow
import React from 'react';
import { IconWarning } from '../../../base/icons';
import { CircularLabel } from '../../../base/label';
import { connect } from '../../../base/redux';
import AbstractInsecureRoomNameLabel, { _mapStateToProps } from '../AbstractInsecureRoomNameLabel';
import styles from './styles';
/**
* Renders a label indicating that we are in a room with an insecure name.
*/
class InsecureRoomNameLabel extends AbstractInsecureRoomNameLabel {
/**
* Renders the platform dependant content.
*
* @inheritdoc
*/
_render() {
return (
<CircularLabel
icon = { IconWarning }
style = { styles.insecureRoomNameLabel } />
);
}
}
export default connect(_mapStateToProps)(InsecureRoomNameLabel);

View File

@ -20,6 +20,8 @@ import AbstractLabels, {
type Props as AbstractLabelsProps
} from '../AbstractLabels';
import { shouldDisplayNotifications } from '../../functions';
import InsecureRoomNameExpandedLabel from './InsecureRoomNameExpandedLabel';
import styles from './styles';
/**
@ -32,14 +34,6 @@ type Props = AbstractLabelsProps & {
*/
t: Function,
/**
* The indicator which determines whether the UI is reduced (to accommodate
* smaller display areas).
*
* @private
*/
_reducedUI: boolean,
/**
* True if the labels should be visible, false otherwise.
*/
@ -85,6 +79,7 @@ const LABEL_ID_QUALITY = 'quality';
const LABEL_ID_RECORDING = 'recording';
const LABEL_ID_STREAMING = 'streaming';
const LABEL_ID_TRANSCRIBING = 'transcribing';
const LABEL_ID_INSECURE_ROOM_NAME = 'insecure-room-name';
/**
* The {@code ExpandedLabel} components to be rendered for the individual
@ -104,7 +99,8 @@ const EXPANDED_LABELS = {
mode: JitsiRecordingConstants.mode.STREAM
}
},
transcribing: TranscribingExpandedLabel
transcribing: TranscribingExpandedLabel,
'insecure-room-name': InsecureRoomNameExpandedLabel
};
/**
@ -159,7 +155,7 @@ class Labels extends AbstractLabels<Props, State> {
}
const wide = !isNarrowAspectRatio(this);
const { _filmstripVisible, _reducedUI } = this.props;
const { _filmstripVisible } = this.props;
return (
<View
@ -200,24 +196,24 @@ class Labels extends AbstractLabels<Props, State> {
this._renderTranscribingLabel()
}
</TouchableOpacity>
{/*
* Emil, Lyubomir, Nichole, and Zoli said that the Labels
* should not be rendered in Picture-in-Picture. Saul
* argued that the recording Labels should be rendered. As
* a temporary compromise, don't render the
* VideoQualityLabel at least because it's not that
* important.
*/
_reducedUI || (
<TouchableOpacity
onLayout = {
this._createOnLayout(LABEL_ID_QUALITY) }
onPress = {
this._createOnPress(LABEL_ID_QUALITY) } >
{ this._renderVideoQualityLabel() }
</TouchableOpacity>
)
}
<TouchableOpacity
onLayout = {
this._createOnLayout(LABEL_ID_INSECURE_ROOM_NAME)
}
onPress = {
this._createOnPress(LABEL_ID_INSECURE_ROOM_NAME)
} >
{
this._renderInsecureRoomNameLabel()
}
</TouchableOpacity>
<TouchableOpacity
onLayout = {
this._createOnLayout(LABEL_ID_QUALITY) }
onPress = {
this._createOnPress(LABEL_ID_QUALITY) } >
{ this._renderVideoQualityLabel() }
</TouchableOpacity>
</View>
<View
style = { [
@ -339,11 +335,13 @@ class Labels extends AbstractLabels<Props, State> {
return null;
}
_renderRecordingLabel: string => React$Element<*>;
_renderRecordingLabel: string => React$Element<any>;
_renderTranscribingLabel: () => React$Element<*>
_renderTranscribingLabel: () => React$Element<any>;
_renderVideoQualityLabel: () => React$Element<*>;
_renderInsecureRoomNameLabel: () => React$Element<any>;
_renderVideoQualityLabel: () => React$Element<any>;
}
/**
@ -352,16 +350,11 @@ class Labels extends AbstractLabels<Props, State> {
*
* @param {Object} state - The redux state.
* @private
* @returns {{
* _filmstripVisible: boolean,
* _reducedUI: boolean,
* _visible: boolean
* }}
* @returns {Props}
*/
function _mapStateToProps(state) {
return {
..._abstractMapStateToProps(state),
_reducedUI: state['features/base/responsive-ui'].reducedUI,
_visible: !shouldDisplayNotifications(state)
};
}

View File

@ -2,3 +2,4 @@
export { default as Conference } from './Conference';
export { default as renderConferenceTimer } from './ConferenceTimerDisplay';
export { default as InsecureRoomNameLabel } from './InsecureRoomNameLabel';

View File

@ -3,6 +3,7 @@ import { ColorSchemeRegistry, schemeColor } from '../../../base/color-scheme';
import { FILMSTRIP_SIZE } from '../../../filmstrip';
export const NAVBAR_GRADIENT_COLORS = [ '#000000FF', '#00000000' ];
export const INSECURE_ROOM_NAME_LABEL_COLOR = ColorPalette.warning;
// From brand guideline
const BOTTOM_GRADIENT_HEIGHT = 290;
@ -167,6 +168,10 @@ export default {
// On iPhone X there is the notch. In the two cases BoxModel.margin is
// not enough.
top: BoxModel.margin * 3
},
insecureRoomNameLabel: {
backgroundColor: INSECURE_ROOM_NAME_LABEL_COLOR
}
};

View File

@ -0,0 +1,35 @@
// @flow
import Tooltip from '@atlaskit/tooltip';
import React from 'react';
import { translate } from '../../../base/i18n';
import { IconWarning } from '../../../base/icons';
import { CircularLabel } from '../../../base/label';
import { connect } from '../../../base/redux';
import AbstractInsecureRoomNameLabel, { _mapStateToProps } from '../AbstractInsecureRoomNameLabel';
/**
* Renders a label indicating that we are in a room with an insecure name.
*/
class InsecureRoomNameLabel extends AbstractInsecureRoomNameLabel {
/**
* Renders the platform dependant content.
*
* @inheritdoc
*/
_render() {
return (
<Tooltip
content = { this.props.t('security.insecureRoomNameWarning') }
position = 'left'>
<CircularLabel
className = 'insecure'
icon = { IconWarning } />
</Tooltip>
);
}
}
export default translate(connect(_mapStateToProps)(InsecureRoomNameLabel));

View File

@ -95,6 +95,9 @@ class Labels extends AbstractLabels<Props, State> {
this.props._showVideoQualityLabel
&& this._renderVideoQualityLabel()
}
{
this._renderInsecureRoomNameLabel()
}
</div>
);
}
@ -107,6 +110,8 @@ class Labels extends AbstractLabels<Props, State> {
_renderTranscribingLabel: () => React$Element<*>;
_renderInsecureRoomNameLabel: () => React$Element<any>;
_renderVideoQualityLabel: () => React$Element<*>;
}

View File

@ -2,4 +2,4 @@
export { default as Conference } from './Conference';
export { default as renderConferenceTimer } from './ConferenceTimerDisplay';
export { default as InsecureRoomNameLabel } from './InsecureRoomNameLabel';

View File

@ -6,6 +6,7 @@ import type { Dispatch } from 'redux';
import { createWelcomePageEvent, sendAnalytics } from '../../analytics';
import { appNavigate } from '../../app';
import isInsecureRoomName from '../../base/util/isInsecureRoomName';
import { isCalendarEnabled } from '../../calendar-sync';
import { isRecentListEnabled } from '../../recent-list/functions';
@ -75,6 +76,7 @@ export class AbstractWelcomePage extends Component<Props, *> {
state = {
animateTimeoutId: undefined,
generatedRoomname: '',
insecureRoomName: false,
joining: false,
room: '',
roomPlaceholder: '',
@ -95,6 +97,7 @@ export class AbstractWelcomePage extends Component<Props, *> {
= this._animateRoomnameChanging.bind(this);
this._onJoin = this._onJoin.bind(this);
this._onRoomChange = this._onRoomChange.bind(this);
this._renderInsecureRoomNameWarning = this._renderInsecureRoomNameWarning.bind(this);
this._updateRoomname = this._updateRoomname.bind(this);
}
@ -160,6 +163,13 @@ export class AbstractWelcomePage extends Component<Props, *> {
clearTimeout(this.state.updateTimeoutId);
}
/**
* Renders the insecure room name warning.
*
* @returns {ReactElement}
*/
_doRenderInsecureRoomNameWarning: () => React$Component<any>;
_onJoin: () => void;
/**
@ -202,7 +212,25 @@ export class AbstractWelcomePage extends Component<Props, *> {
* @returns {void}
*/
_onRoomChange(value: string) {
this.setState({ room: value });
this.setState({
room: value,
insecureRoomName: value && isInsecureRoomName(value)
});
}
_renderInsecureRoomNameWarning: () => React$Component<any>;;
/**
* Renders the insecure room name warning if needed.
*
* @returns {ReactElement}
*/
_renderInsecureRoomNameWarning() {
if (this.state.insecureRoomName) {
return this._doRenderInsecureRoomNameWarning();
}
return null;
}
_updateRoomname: () => void;

View File

@ -13,7 +13,7 @@ import { getName } from '../../app';
import { ColorSchemeRegistry } from '../../base/color-scheme';
import { translate } from '../../base/i18n';
import { Icon, IconMenu } from '../../base/icons';
import { Icon, IconMenu, IconWarning } from '../../base/icons';
import { MEDIA_TYPE } from '../../base/media';
import { Header, LoadingIndicator, Text } from '../../base/react';
import { connect } from '../../base/redux';
@ -119,6 +119,28 @@ class WelcomePage extends AbstractWelcomePage {
return this._renderFullUI();
}
/**
* Renders the insecure room name warning.
*
* @inheritdoc
*/
_doRenderInsecureRoomNameWarning() {
return (
<View
style = { [
styles.messageContainer,
styles.insecureRoomNameWarningContainer
] }>
<Icon
src = { IconWarning }
style = { styles.insecureRoomNameWarningIcon } />
<Text style = { styles.insecureRoomNameWarningText }>
{ this.props.t('security.insecureRoomNameWarning') }
</Text>
</View>
);
}
/**
* Constructs a style array to handle the hint box animation.
*
@ -127,6 +149,7 @@ class WelcomePage extends AbstractWelcomePage {
*/
_getHintBoxStyle() {
return [
styles.messageContainer,
styles.hintContainer,
{
opacity: this.state.hintBoxAnimation
@ -283,6 +306,9 @@ class WelcomePage extends AbstractWelcomePage {
style = { styles.textInput }
underlineColorAndroid = 'transparent'
value = { this.state.room } />
{
this._renderInsecureRoomNameWarning()
}
{
this._renderHintBox()
}

View File

@ -3,6 +3,7 @@
import React from 'react';
import { translate } from '../../base/i18n';
import { Icon, IconWarning } from '../../base/icons';
import { Watermarks } from '../../base/react';
import { connect } from '../../base/redux';
import { isMobileBrowser } from '../../base/environment/utils';
@ -209,6 +210,7 @@ class WelcomePage extends AbstractWelcomePage {
title = { t('welcomepage.roomNameAllowedChars') }
type = 'text'
value = { this.state.room } />
{ this._renderInsecureRoomNameWarning() }
</form>
</div>
<div
@ -233,6 +235,22 @@ class WelcomePage extends AbstractWelcomePage {
);
}
/**
* Renders the insecure room name warning.
*
* @inheritdoc
*/
_doRenderInsecureRoomNameWarning() {
return (
<div className = 'insecure-room-name-warning'>
<Icon src = { IconWarning } />
<span>
{ this.props.t('security.insecureRoomNameWarning') }
</span>
</div>
);
}
/**
* Prevents submission of the form and delegates join logic.
*

View File

@ -108,15 +108,8 @@ export default {
* Container for the hint box.
*/
hintContainer: {
backgroundColor: ColorPalette.white,
borderColor: ColorPalette.white,
borderRadius: 4,
borderWidth: 1,
flexDirection: 'column',
marginVertical: 5,
overflow: 'hidden',
paddingHorizontal: BoxModel.padding,
paddingVertical: 2 * BoxModel.padding
overflow: 'hidden'
},
/**
@ -148,6 +141,16 @@ export default {
padding: BoxModel.padding
},
messageContainer: {
backgroundColor: ColorPalette.white,
borderColor: ColorPalette.white,
borderRadius: 4,
borderWidth: 1,
marginVertical: 5,
paddingHorizontal: BoxModel.padding,
paddingVertical: 2 * BoxModel.padding
},
/**
* The style of the top-level container/{@code View} of
* {@code LocalVideoTrackUnderlay}.
@ -280,6 +283,23 @@ export default {
textAlign: 'center'
},
insecureRoomNameWarningContainer: {
alignItems: 'center',
flexDirection: 'row',
paddingHorizontal: 5
},
insecureRoomNameWarningIcon: {
color: ColorPalette.warning,
fontSize: 24,
marginRight: 10
},
insecureRoomNameWarningText: {
color: ColorPalette.warning,
flex: 1
},
/**
* The style of the top-level container of {@code WelcomePage}.
*/

View File

@ -179,7 +179,7 @@ module.exports = [
entry: {
'app.bundle': './app.js'
},
performance: getPerformanceHints(3 * 1024 * 1024)
performance: getPerformanceHints(4 * 1024 * 1024)
}),
Object.assign({}, config, {
entry: {