diff --git a/res/css/_components.scss b/res/css/_components.scss index e017d4b95a..ee55c000ff 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -107,6 +107,7 @@ @import "./views/rooms/_AppsDrawer.scss"; @import "./views/rooms/_Autocomplete.scss"; @import "./views/rooms/_AuxPanel.scss"; +@import "./views/rooms/_E2EIcon.scss"; @import "./views/rooms/_EntityTile.scss"; @import "./views/rooms/_EventTile.scss"; @import "./views/rooms/_JumpToBottomButton.scss"; diff --git a/res/css/structures/_RoomStatusBar.scss b/res/css/structures/_RoomStatusBar.scss index 2a9cc9f6c7..1054654670 100644 --- a/res/css/structures/_RoomStatusBar.scss +++ b/res/css/structures/_RoomStatusBar.scss @@ -121,7 +121,7 @@ limitations under the License. .mx_RoomStatusBar_connectionLostBar img { padding-left: 10px; - padding-right: 22px; + padding-right: 10px; vertical-align: middle; float: left; } diff --git a/res/css/views/rooms/_E2EIcon.scss b/res/css/views/rooms/_E2EIcon.scss new file mode 100644 index 0000000000..cd577df87b --- /dev/null +++ b/res/css/views/rooms/_E2EIcon.scss @@ -0,0 +1,33 @@ +/* +Copyright 2019 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_E2EIcon { + width: 25px; + height: 25px; + mask-repeat: no-repeat; + mask-position: center 0; + margin: 0 9px; +} + +.mx_E2EIcon_verified { + mask-image: url('$(res)/img/feather-icons/e2e/lock-verified.svg'); + background-color: $accent-color; +} + +.mx_E2EIcon_warning { + mask-image: url('$(res)/img/feather-icons/e2e/lock-warning.svg'); + background-color: $warning-color; +} diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index c920d6e390..7c9a6babea 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -281,9 +281,24 @@ limitations under the License. .mx_EventTile_e2eIcon { display: block; position: absolute; - top: 9px; + top: 8px; left: 46px; + width: 15px; + height: 15px; cursor: pointer; + mask-size: 14px; + mask-repeat: no-repeat; + mask-position: 0; +} + +.mx_EventTile_e2eIcon_undecryptable, .mx_EventTile_e2eIcon_unverified { + mask-image: url('$(res)/img/feather-icons/e2e/warning.svg'); + background-color: $warning-color; +} + +.mx_EventTile_e2eIcon_unencrypted { + mask-image: url('$(res)/img/feather-icons/e2e/warning.svg'); + background-color: $composer-e2e-icon-color; } .mx_EventTile_e2eIcon_hidden { diff --git a/res/css/views/rooms/_MemberDeviceInfo.scss b/res/css/views/rooms/_MemberDeviceInfo.scss index 6ccc4c7ae3..f780c50410 100644 --- a/res/css/views/rooms/_MemberDeviceInfo.scss +++ b/res/css/views/rooms/_MemberDeviceInfo.scss @@ -20,6 +20,25 @@ limitations under the License. align-items: start; } +.mx_MemberDeviceInfo_icon { + margin-top: 4px; + width: 12px; + height: 12px; + mask-repeat: no-repeat; +} +.mx_MemberDeviceInfo_icon_blacklisted { + mask-image: url('$(res)/img/feather-icons/e2e/blacklisted.svg'); + background-color: $warning-color; +} +.mx_MemberDeviceInfo_icon_verified { + mask-image: url('$(res)/img/feather-icons/e2e/verified.svg'); + background-color: $accent-color; +} +.mx_MemberDeviceInfo_icon_unverified { + mask-image: url('$(res)/img/feather-icons/e2e/warning.svg'); + background-color: $warning-color; +} + .mx_MemberDeviceInfo > .mx_DeviceVerifyButtons { display: flex; flex-direction: column; diff --git a/res/css/views/rooms/_MemberInfo.scss b/res/css/views/rooms/_MemberInfo.scss index 99771fece0..707c186518 100644 --- a/res/css/views/rooms/_MemberInfo.scss +++ b/res/css/views/rooms/_MemberInfo.scss @@ -26,6 +26,10 @@ limitations under the License. display: flex; } +.mx_MemberInfo_name > .mx_E2EIcon { + margin-left: 0; +} + .mx_MemberInfo_cancel { height: 16px; padding: 10px 15px; diff --git a/res/css/views/rooms/_MessageComposer.scss b/res/css/views/rooms/_MessageComposer.scss index dc4aec691b..7ee7efcaff 100644 --- a/res/css/views/rooms/_MessageComposer.scss +++ b/res/css/views/rooms/_MessageComposer.scss @@ -23,6 +23,10 @@ limitations under the License. padding-left: 84px; } +.mx_MessageComposer_wrapper.mx_MessageComposer_hasE2EIcon { + padding-left: 109px; +} + .mx_MessageComposer_replaced_wrapper { margin-left: auto; margin-right: auto; @@ -71,9 +75,10 @@ limitations under the License. width: 100%; } -.mx_MessageComposer_e2eIcon { +.mx_MessageComposer_e2eIcon.mx_E2EIcon { position: absolute; left: 60px; + background-color: $composer-e2e-icon-color; } .mx_MessageComposer_noperm_error { diff --git a/res/img/feather-icons/e2e/blacklisted.svg b/res/img/feather-icons/e2e/blacklisted.svg new file mode 100644 index 0000000000..63897c2227 --- /dev/null +++ b/res/img/feather-icons/e2e/blacklisted.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/res/img/feather-icons/e2e/lock-verified.svg b/res/img/feather-icons/e2e/lock-verified.svg new file mode 100644 index 0000000000..4cd4684ea2 --- /dev/null +++ b/res/img/feather-icons/e2e/lock-verified.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/res/img/feather-icons/e2e/lock-warning.svg b/res/img/feather-icons/e2e/lock-warning.svg new file mode 100644 index 0000000000..507c532f9d --- /dev/null +++ b/res/img/feather-icons/e2e/lock-warning.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/res/img/feather-icons/e2e/verified.svg b/res/img/feather-icons/e2e/verified.svg new file mode 100644 index 0000000000..f143f854e6 --- /dev/null +++ b/res/img/feather-icons/e2e/verified.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/feather-icons/e2e/warning.svg b/res/img/feather-icons/e2e/warning.svg new file mode 100644 index 0000000000..e6c246dba9 --- /dev/null +++ b/res/img/feather-icons/e2e/warning.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/res/themes/dharma/css/_dharma.scss b/res/themes/dharma/css/_dharma.scss index f976180144..482b3c51cb 100644 --- a/res/themes/dharma/css/_dharma.scss +++ b/res/themes/dharma/css/_dharma.scss @@ -142,6 +142,8 @@ $roomheader-addroom-color: #91A1C0; $roomtopic-color: #9fa9ba; $eventtile-meta-color: $roomtopic-color; +$composer-e2e-icon-color: #c9ced6; + // ******************** $roomtile-name-color: #61708b; diff --git a/res/themes/light/css/_base.scss b/res/themes/light/css/_base.scss index 998325e1b7..8185ba0ace 100644 --- a/res/themes/light/css/_base.scss +++ b/res/themes/light/css/_base.scss @@ -134,6 +134,9 @@ $roomheader-color: $primary-fg-color; $roomheader-addroom-color: $primary-bg-color; $roomtopic-color: $settings-grey-fg-color; $eventtile-meta-color: $roomtopic-color; + +$composer-e2e-icon-color: #c9ced6; + // ******************** $roomtile-name-color: rgba(69, 69, 69, 0.8); diff --git a/src/components/structures/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js index b57bac805e..ab7f472931 100644 --- a/src/components/structures/RoomStatusBar.js +++ b/src/components/structures/RoomStatusBar.js @@ -290,7 +290,7 @@ module.exports = React.createClass({ } return
- +
{ title } @@ -309,7 +309,7 @@ module.exports = React.createClass({ if (this._shouldShowConnectionError()) { return (
- /!\ + /!\
{ _t('Connectivity to the server has been lost.') } diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 26ef3da739..b6513f0418 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -168,6 +168,7 @@ module.exports = React.createClass({ MatrixClientPeg.get().on("Room.myMembership", this.onMyMembership); MatrixClientPeg.get().on("accountData", this.onAccountData); MatrixClientPeg.get().on("crypto.keyBackupStatus", this.onKeyBackupStatus); + MatrixClientPeg.get().on("deviceVerificationChanged", this.onDeviceVerificationChanged); this._fetchMediaConfig(); // Start listening for RoomViewStore updates this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate); @@ -457,6 +458,7 @@ module.exports = React.createClass({ MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember); MatrixClientPeg.get().removeListener("accountData", this.onAccountData); MatrixClientPeg.get().removeListener("crypto.keyBackupStatus", this.onKeyBackupStatus); + MatrixClientPeg.get().removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged); } window.removeEventListener('beforeunload', this.onPageUnload); @@ -589,6 +591,10 @@ module.exports = React.createClass({ this._updatePreviewUrlVisibility(room); } + if (ev.getType() === "m.room.encryption") { + this._updateE2EStatus(room); + } + // ignore anything but real-time updates at the end of the room: // updates from pagination will happen when the paginate completes. if (toStartOfTimeline || !data || !data.liveEvent) return; @@ -642,6 +648,7 @@ module.exports = React.createClass({ this._updatePreviewUrlVisibility(room); this._loadMembersIfJoined(room); this._calculateRecommendedVersion(room); + this._updateE2EStatus(room); }, _calculateRecommendedVersion: async function(room) { @@ -733,6 +740,23 @@ module.exports = React.createClass({ }); }, + onDeviceVerificationChanged: function(userId, device) { + const room = this.state.room; + if (!room.currentState.getMember(userId)) { + return; + } + this._updateE2EStatus(room); + }, + + _updateE2EStatus: function(room) { + if (!MatrixClientPeg.get().isRoomEncrypted(room.roomId)) { + return; + } + room.hasUnverifiedDevices().then((hasUnverifiedDevices) => { + this.setState({e2eStatus: hasUnverifiedDevices ? "warning" : "verified"}); + }); + }, + updateTint: function() { const room = this.state.room; if (!room) return; @@ -1575,6 +1599,7 @@ module.exports = React.createClass({ room={this.state.room} oobData={this.props.oobData} collapsedRhs={this.props.collapsedRhs} + e2eStatus={this.state.e2eStatus} />
@@ -1622,6 +1647,7 @@ module.exports = React.createClass({ ref="header" room={this.state.room} collapsedRhs={this.props.collapsedRhs} + e2eStatus={this.state.e2eStatus} />
@@ -1767,6 +1793,7 @@ module.exports = React.createClass({ disabled={this.props.disabled} showApps={this.state.showApps} uploadAllowed={this.isFileUploadAllowed} + e2eStatus={this.state.e2eStatus} />; } @@ -1917,6 +1944,7 @@ module.exports = React.createClass({ onCancelClick={(aux && !hideCancel) ? this.onCancelClick : null} onForgetClick={(myMembership === "leave") ? this.onForgetClick : null} onLeaveClick={(myMembership === "join") ? this.onLeaveClick : null} + e2eStatus={this.state.e2eStatus} />
diff --git a/src/components/views/rooms/E2EIcon.js b/src/components/views/rooms/E2EIcon.js new file mode 100644 index 0000000000..30891e84b7 --- /dev/null +++ b/src/components/views/rooms/E2EIcon.js @@ -0,0 +1,39 @@ +/* +Copyright 2019 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import classNames from 'classnames'; +import { _t } from '../../../languageHandler'; + +export default function(props) { + const isWarning = props.status === "warning"; + const isVerified = props.status === "verified"; + const e2eIconClasses = classNames({ + mx_E2EIcon: true, + mx_E2EIcon_warning: isWarning, + mx_E2EIcon_verified: isVerified, + }, props.className); + let e2eTitle; + if (isWarning) { + e2eTitle = props.isUser ? + _t("Some devices for this user are not trusted") : + _t("Some devices in this encrypted room are not trusted"); + } else if (isVerified) { + e2eTitle = props.isUser ? + _t("All devices for this user are trusted") : + _t("All devices in this encrypted room are trusted"); + } + return (
); +} diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index acb122ad4e..e4a6695ff5 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -459,17 +459,21 @@ module.exports = withMatrixClient(React.createClass({ // event is encrypted, display padlock corresponding to whether or not it is verified if (ev.isEncrypted()) { - return this.state.verified ? : ; + if (this.state.verified) { + return; // no icon for verified + } else { + return (); + } } if (this.props.matrixClient.isRoomEncrypted(ev.getRoomId())) { // else if room is encrypted // and event is being encrypted or is not_sent (Unknown Devices/Network Error) if (ev.status === EventStatus.ENCRYPTING) { - return ; + return; } if (ev.status === EventStatus.NOT_SENT) { - return ; + return; } // if the event is not encrypted, but it's an e2e room, show the open padlock return ; @@ -767,57 +771,29 @@ module.exports.haveTileForEvent = function(e) { function E2ePadlockUndecryptable(props) { return ( - - ); -} - -function E2ePadlockEncrypting(props) { - return ( - - ); -} - -function E2ePadlockNotSent(props) { - return ( - - ); -} - -function E2ePadlockVerified(props) { - return ( - + ); } function E2ePadlockUnverified(props) { return ( - + ); } function E2ePadlockUnencrypted(props) { return ( - + ); } function E2ePadlock(props) { if (SettingsStore.getValue("alwaysShowEncryptionIcons")) { - return ; + return
; } else { - return ; + return
; } } diff --git a/src/components/views/rooms/MemberDeviceInfo.js b/src/components/views/rooms/MemberDeviceInfo.js index b9c276f0d1..67f99e4d1d 100644 --- a/src/components/views/rooms/MemberDeviceInfo.js +++ b/src/components/views/rooms/MemberDeviceInfo.js @@ -18,32 +18,18 @@ import React from 'react'; import PropTypes from 'prop-types'; import sdk from '../../../index'; import { _t } from '../../../languageHandler'; +import classNames from 'classnames'; export default class MemberDeviceInfo extends React.Component { render() { - let indicator = null; const DeviceVerifyButtons = sdk.getComponent('elements.DeviceVerifyButtons'); - - if (this.props.device.isBlocked()) { - indicator = ( -
- {_t("Blacklisted")} -
- ); - } else if (this.props.device.isVerified()) { - indicator = ( -
- {_t("Verified")} -
- ); - } else { - indicator = ( -
- {_t("Unverified")} -
- ); - } - + const iconClasses = classNames({ + mx_MemberDeviceInfo_icon: true, + mx_MemberDeviceInfo_icon_blacklisted: this.props.device.isBlocked(), + mx_MemberDeviceInfo_icon_verified: this.props.device.isVerified(), + mx_MemberDeviceInfo_icon_unverified: this.props.device.isUnverified(), + }); + const indicator = (
); const deviceName = this.props.device.ambiguous ? (this.props.device.getDisplayName() ? this.props.device.getDisplayName() : "") + " (" + this.props.device.deviceId + ")" : this.props.device.getDisplayName(); @@ -52,10 +38,10 @@ export default class MemberDeviceInfo extends React.Component { return (
+ { indicator }
{ deviceName } - { indicator }
diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index 9859861870..5df0da7491 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -43,6 +43,7 @@ import RoomViewStore from '../../../stores/RoomViewStore'; import SdkConfig from '../../../SdkConfig'; import MultiInviter from "../../../utils/MultiInviter"; import SettingsStore from "../../../settings/SettingsStore"; +import E2EIcon from "./E2EIcon"; module.exports = withMatrixClient(React.createClass({ displayName: 'MemberInfo', @@ -153,11 +154,19 @@ module.exports = withMatrixClient(React.createClass({ // Promise.resolve to handle transition from static result to promise; can be removed // in future Promise.resolve(this.props.matrixClient.getStoredDevicesForUser(userId)).then((devices) => { - this.setState({devices: devices}); + this.setState({ + devices: devices, + e2eStatus: this._getE2EStatus(devices), + }); }); } }, + _getE2EStatus: function(devices) { + const hasUnverifiedDevice = devices.some((device) => device.isUnverified()); + return hasUnverifiedDevice ? "warning" : "verified"; + }, + onRoom: function(room) { this.forceUpdate(); }, @@ -234,8 +243,13 @@ module.exports = withMatrixClient(React.createClass({ // we got cancelled - presumably a different user now return; } + self._disambiguateDevices(devices); - self.setState({devicesLoading: false, devices: devices}); + self.setState({ + devicesLoading: false, + devices: devices, + e2eStatus: self._getE2EStatus(devices), + }); }, function(err) { console.log("Error downloading devices", err); self.setState({devicesLoading: false}); @@ -965,6 +979,7 @@ module.exports = withMatrixClient(React.createClass({ {_t('Close')} + { this.state.e2eStatus ? : undefined } { memberName }
{ avatarElement } diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 7681c2dc13..7117825d76 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -26,6 +26,9 @@ import RoomViewStore from '../../../stores/RoomViewStore'; import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore"; import Stickerpicker from './Stickerpicker'; import { makeRoomPermalink } from '../../../matrix-to'; +import classNames from 'classnames'; + +import E2EIcon from './E2EIcon'; const formatButtonList = [ _td("bold"), @@ -316,25 +319,14 @@ export default class MessageComposer extends React.Component { ); } - let e2eImg; let e2eTitle; let e2eClass; - const roomIsEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId); - if (roomIsEncrypted) { - // FIXME: show a /!\ if there are untrusted devices in the room... - e2eImg = require("../../../../res/img/e2e-verified.svg"); - e2eTitle = _t('Encrypted room'); - e2eClass = 'mx_MessageComposer_e2eIcon'; - } else { - e2eImg = require("../../../../res/img/e2e-unencrypted.svg"); - e2eTitle = _t('Unencrypted room'); - e2eClass = 'mx_MessageComposer_e2eIcon mx_filterFlipColor'; + if (this.props.e2eStatus) { + controls.push( + ); } - controls.push( - {e2eTitle}, - ); - let callButton; let videoCallButton; let hangupButton; @@ -413,6 +405,7 @@ export default class MessageComposer extends React.Component { key="controls_formatting" /> ) : null; + const roomIsEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId); let placeholderText; if (this.state.isQuoting) { if (roomIsEncrypted) { @@ -509,9 +502,13 @@ export default class MessageComposer extends React.Component {
; } + const wrapperClasses = classNames({ + mx_MessageComposer_wrapper: true, + mx_MessageComposer_hasE2EIcon: !!this.props.e2eStatus, + }); return (
-
+
{ controls }
diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 3cdb9237be..cee1814011 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -33,6 +33,7 @@ import ManageIntegsButton from '../elements/ManageIntegsButton'; import {CancelButton} from './SimpleRoomHeader'; import SettingsStore from "../../../settings/SettingsStore"; import RoomHeaderButtons from '../right_panel/RoomHeaderButtons'; +import E2EIcon from './E2EIcon'; linkifyMatrix(linkify); @@ -52,6 +53,7 @@ module.exports = React.createClass({ onSearchClick: PropTypes.func, onLeaveClick: PropTypes.func, onCancelClick: PropTypes.func, + e2eStatus: PropTypes.string, }, getDefaultProps: function() { @@ -237,6 +239,10 @@ module.exports = React.createClass({ ); } + const e2eIcon = this.props.e2eStatus ? + : + undefined; + if (this.props.onCancelClick) { cancelButton = ; } @@ -413,6 +419,7 @@ module.exports = React.createClass({
{ roomAvatar }
+ { e2eIcon } { name } { topicElement } { spinner } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 8fe1b926f9..f1d7296d03 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -571,6 +571,10 @@ " (unsupported)": " (unsupported)", "Join as voice or video.": "Join as voice or video.", "Ongoing conference call%(supportedText)s.": "Ongoing conference call%(supportedText)s.", + "Some devices for this user are not trusted": "Some devices for this user are not trusted", + "Some devices in this encrypted room are not trusted": "Some devices in this encrypted room are not trusted", + "All devices for this user are trusted": "All devices for this user are trusted", + "All devices in this encrypted room are trusted": "All devices in this encrypted room are trusted", "This event could not be displayed": "This event could not be displayed", "%(senderName)s sent an image": "%(senderName)s sent an image", "%(senderName)s sent a video": "%(senderName)s sent a video", @@ -582,16 +586,10 @@ "Key request sent.": "Key request sent.", "Re-request encryption keys from your other devices.": "Re-request encryption keys from your other devices.", "Undecryptable": "Undecryptable", - "Encrypting": "Encrypting", - "Encrypted, not sent": "Encrypted, not sent", - "Encrypted by a verified device": "Encrypted by a verified device", "Encrypted by an unverified device": "Encrypted by an unverified device", "Unencrypted message": "Unencrypted message", "Please select the destination room for this message": "Please select the destination room for this message", "Scroll to bottom of page": "Scroll to bottom of page", - "Blacklisted": "Blacklisted", - "Verified": "Verified", - "Unverified": "Unverified", "device id: ": "device id: ", "Disinvite": "Disinvite", "Kick": "Kick", @@ -643,8 +641,6 @@ "Are you sure you want to upload the following files?": "Are you sure you want to upload the following files?", "The following files cannot be uploaded:": "The following files cannot be uploaded:", "Upload Files": "Upload Files", - "Encrypted room": "Encrypted room", - "Unencrypted room": "Unencrypted room", "Hangup": "Hangup", "Voice call": "Voice call", "Video call": "Video call", @@ -1437,6 +1433,7 @@ "Users": "Users", "unknown device": "unknown device", "NOT verified": "NOT verified", + "Blacklisted": "Blacklisted", "verified": "verified", "Name": "Name", "Verification": "Verification",