Device manage - handle sessions that don't support encryption (#9717)
* add handling for unverifiable sessions * test * update types for filtervariation * strict fixes * avoid setting up cross signing in device man testspull/28788/head^2
parent
6150b86421
commit
888e69f39a
|
@ -49,7 +49,6 @@ describe("Device manager", () => {
|
|||
|
||||
cy.get('[data-testid="current-session-section"]').within(() => {
|
||||
cy.contains('Unverified session').should('exist');
|
||||
cy.get('.mx_DeviceSecurityCard_actions [role="button"]').should('exist');
|
||||
});
|
||||
|
||||
// current session details opened
|
||||
|
|
|
@ -32,6 +32,7 @@ const VariationIcon: Record<DeviceSecurityVariation, React.FC<React.SVGProps<SVG
|
|||
[DeviceSecurityVariation.Inactive]: InactiveIcon,
|
||||
[DeviceSecurityVariation.Verified]: VerifiedIcon,
|
||||
[DeviceSecurityVariation.Unverified]: UnverifiedIcon,
|
||||
[DeviceSecurityVariation.Unverifiable]: UnverifiedIcon,
|
||||
};
|
||||
|
||||
const DeviceSecurityIcon: React.FC<{ variation: DeviceSecurityVariation }> = ({ variation }) => {
|
||||
|
|
|
@ -56,6 +56,26 @@ const securityCardContent: Record<DeviceSecurityVariation, {
|
|||
</p>
|
||||
</>,
|
||||
},
|
||||
// unverifiable uses single-session case
|
||||
// because it is only ever displayed on a single session detail
|
||||
[DeviceSecurityVariation.Unverifiable]: {
|
||||
title: _t('Unverified session'),
|
||||
description: <>
|
||||
<p>{ _t(`This session doesn't support encryption, so it can't be verified.`) }
|
||||
</p>
|
||||
<p>
|
||||
{ _t(
|
||||
`You won't be able to participate in rooms where encryption is enabled when using this session.`,
|
||||
)
|
||||
}
|
||||
</p><p>
|
||||
{ _t(
|
||||
`For best security and privacy, it is recommended to use Matrix clients that support encryption.`,
|
||||
)
|
||||
}
|
||||
</p>
|
||||
</>,
|
||||
},
|
||||
[DeviceSecurityVariation.Inactive]: {
|
||||
title: _t('Inactive sessions'),
|
||||
description: <>
|
||||
|
|
|
@ -30,18 +30,33 @@ interface Props {
|
|||
onVerifyDevice?: () => void;
|
||||
}
|
||||
|
||||
export const DeviceVerificationStatusCard: React.FC<Props> = ({
|
||||
device,
|
||||
onVerifyDevice,
|
||||
}) => {
|
||||
const securityCardProps = device.isVerified ? {
|
||||
variation: DeviceSecurityVariation.Verified,
|
||||
heading: _t('Verified session'),
|
||||
description: <>
|
||||
{ _t('This session is ready for secure messaging.') }
|
||||
<DeviceSecurityLearnMore variation={DeviceSecurityVariation.Verified} />
|
||||
</>,
|
||||
} : {
|
||||
const getCardProps = (device: ExtendedDevice): {
|
||||
variation: DeviceSecurityVariation;
|
||||
heading: string;
|
||||
description: React.ReactNode;
|
||||
} => {
|
||||
if (device.isVerified) {
|
||||
return {
|
||||
variation: DeviceSecurityVariation.Verified,
|
||||
heading: _t('Verified session'),
|
||||
description: <>
|
||||
{ _t('This session is ready for secure messaging.') }
|
||||
<DeviceSecurityLearnMore variation={DeviceSecurityVariation.Verified} />
|
||||
</>,
|
||||
};
|
||||
}
|
||||
if (device.isVerified === null) {
|
||||
return {
|
||||
variation: DeviceSecurityVariation.Unverified,
|
||||
heading: _t('Unverified session'),
|
||||
description: <>
|
||||
{ _t(`This session doesn't support encryption and thus can't be verified.`) }
|
||||
<DeviceSecurityLearnMore variation={DeviceSecurityVariation.Unverifiable} />
|
||||
</>,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
variation: DeviceSecurityVariation.Unverified,
|
||||
heading: _t('Unverified session'),
|
||||
description: <>
|
||||
|
@ -49,10 +64,19 @@ export const DeviceVerificationStatusCard: React.FC<Props> = ({
|
|||
<DeviceSecurityLearnMore variation={DeviceSecurityVariation.Unverified} />
|
||||
</>,
|
||||
};
|
||||
};
|
||||
|
||||
export const DeviceVerificationStatusCard: React.FC<Props> = ({
|
||||
device,
|
||||
onVerifyDevice,
|
||||
}) => {
|
||||
const securityCardProps = getCardProps(device);
|
||||
|
||||
return <DeviceSecurityCard
|
||||
{...securityCardProps}
|
||||
>
|
||||
{ !device.isVerified && !!onVerifyDevice &&
|
||||
{ /* check for explicit false to exclude unverifiable devices */ }
|
||||
{ device.isVerified === false && !!onVerifyDevice &&
|
||||
<AccessibleButton
|
||||
kind='primary'
|
||||
onClick={onVerifyDevice}
|
||||
|
|
|
@ -27,6 +27,7 @@ import { DeviceExpandDetailsButton } from './DeviceExpandDetailsButton';
|
|||
import DeviceSecurityCard from './DeviceSecurityCard';
|
||||
import {
|
||||
filterDevicesBySecurityRecommendation,
|
||||
FilterVariation,
|
||||
INACTIVE_DEVICE_AGE_DAYS,
|
||||
} from './filter';
|
||||
import SelectableDeviceTile from './SelectableDeviceTile';
|
||||
|
@ -47,8 +48,8 @@ interface Props {
|
|||
expandedDeviceIds: ExtendedDevice['device_id'][];
|
||||
signingOutDeviceIds: ExtendedDevice['device_id'][];
|
||||
selectedDeviceIds: ExtendedDevice['device_id'][];
|
||||
filter?: DeviceSecurityVariation;
|
||||
onFilterChange: (filter: DeviceSecurityVariation | undefined) => void;
|
||||
filter?: FilterVariation;
|
||||
onFilterChange: (filter: FilterVariation | undefined) => void;
|
||||
onDeviceExpandToggle: (deviceId: ExtendedDevice['device_id']) => void;
|
||||
onSignOutDevices: (deviceIds: ExtendedDevice['device_id'][]) => void;
|
||||
saveDeviceName: DevicesState['saveDeviceName'];
|
||||
|
@ -68,12 +69,12 @@ const sortDevicesByLatestActivityThenDisplayName = (left: ExtendedDevice, right:
|
|||
(right.last_seen_ts || 0) - (left.last_seen_ts || 0)
|
||||
|| ((left.display_name || left.device_id).localeCompare(right.display_name || right.device_id));
|
||||
|
||||
const getFilteredSortedDevices = (devices: DevicesDictionary, filter?: DeviceSecurityVariation) =>
|
||||
const getFilteredSortedDevices = (devices: DevicesDictionary, filter?: FilterVariation) =>
|
||||
filterDevicesBySecurityRecommendation(Object.values(devices), filter ? [filter] : [])
|
||||
.sort(sortDevicesByLatestActivityThenDisplayName);
|
||||
|
||||
const ALL_FILTER_ID = 'ALL';
|
||||
type DeviceFilterKey = DeviceSecurityVariation | typeof ALL_FILTER_ID;
|
||||
type DeviceFilterKey = FilterVariation | typeof ALL_FILTER_ID;
|
||||
|
||||
const securityCardContent: Record<DeviceSecurityVariation, {
|
||||
title: string;
|
||||
|
@ -90,6 +91,12 @@ const securityCardContent: Record<DeviceSecurityVariation, {
|
|||
`sign out from those you don't recognize or use anymore.`,
|
||||
),
|
||||
},
|
||||
[DeviceSecurityVariation.Unverifiable]: {
|
||||
title: _t('Unverified session'),
|
||||
description: _t(
|
||||
`This session doesn't support encryption and thus can't be verified.`,
|
||||
),
|
||||
},
|
||||
[DeviceSecurityVariation.Inactive]: {
|
||||
title: _t('Inactive sessions'),
|
||||
description: _t(
|
||||
|
@ -100,8 +107,12 @@ const securityCardContent: Record<DeviceSecurityVariation, {
|
|||
},
|
||||
};
|
||||
|
||||
const isSecurityVariation = (filter?: DeviceFilterKey): filter is DeviceSecurityVariation =>
|
||||
Object.values<string>(DeviceSecurityVariation).includes(filter);
|
||||
const isSecurityVariation = (filter?: DeviceFilterKey): filter is FilterVariation =>
|
||||
!!filter && ([
|
||||
DeviceSecurityVariation.Inactive,
|
||||
DeviceSecurityVariation.Unverified,
|
||||
DeviceSecurityVariation.Verified,
|
||||
] as string[]).includes(filter);
|
||||
|
||||
const FilterSecurityCard: React.FC<{ filter?: DeviceFilterKey }> = ({ filter }) => {
|
||||
if (isSecurityVariation(filter)) {
|
||||
|
@ -124,7 +135,7 @@ const FilterSecurityCard: React.FC<{ filter?: DeviceFilterKey }> = ({ filter })
|
|||
return null;
|
||||
};
|
||||
|
||||
const getNoResultsMessage = (filter?: DeviceSecurityVariation): string => {
|
||||
const getNoResultsMessage = (filter?: FilterVariation): string => {
|
||||
switch (filter) {
|
||||
case DeviceSecurityVariation.Verified:
|
||||
return _t('No verified sessions found.');
|
||||
|
@ -136,7 +147,7 @@ const getNoResultsMessage = (filter?: DeviceSecurityVariation): string => {
|
|||
return _t('No sessions found.');
|
||||
}
|
||||
};
|
||||
interface NoResultsProps { filter?: DeviceSecurityVariation, clearFilter: () => void}
|
||||
interface NoResultsProps { filter?: FilterVariation, clearFilter: () => void}
|
||||
const NoResults: React.FC<NoResultsProps> = ({ filter, clearFilter }) =>
|
||||
<div className='mx_FilteredDeviceList_noResults'>
|
||||
{ getNoResultsMessage(filter) }
|
||||
|
@ -273,7 +284,7 @@ export const FilteredDeviceList =
|
|||
];
|
||||
|
||||
const onFilterOptionChange = (filterId: DeviceFilterKey) => {
|
||||
onFilterChange(filterId === ALL_FILTER_ID ? undefined : filterId as DeviceSecurityVariation);
|
||||
onFilterChange(filterId === ALL_FILTER_ID ? undefined : filterId as FilterVariation);
|
||||
};
|
||||
|
||||
const isAllSelected = selectedDeviceIds.length >= sortedDevices.length;
|
||||
|
|
|
@ -21,7 +21,7 @@ import AccessibleButton from '../../elements/AccessibleButton';
|
|||
import SettingsSubsection from '../shared/SettingsSubsection';
|
||||
import DeviceSecurityCard from './DeviceSecurityCard';
|
||||
import { DeviceSecurityLearnMore } from './DeviceSecurityLearnMore';
|
||||
import { filterDevicesBySecurityRecommendation, INACTIVE_DEVICE_AGE_DAYS } from './filter';
|
||||
import { filterDevicesBySecurityRecommendation, FilterVariation, INACTIVE_DEVICE_AGE_DAYS } from './filter';
|
||||
import {
|
||||
DeviceSecurityVariation,
|
||||
ExtendedDevice,
|
||||
|
@ -31,7 +31,7 @@ import {
|
|||
interface Props {
|
||||
devices: DevicesDictionary;
|
||||
currentDeviceId: ExtendedDevice['device_id'];
|
||||
goToFilteredList: (filter: DeviceSecurityVariation) => void;
|
||||
goToFilteredList: (filter: FilterVariation) => void;
|
||||
}
|
||||
|
||||
const SecurityRecommendations: React.FC<Props> = ({
|
||||
|
|
|
@ -22,10 +22,14 @@ const MS_DAY = 24 * 60 * 60 * 1000;
|
|||
export const INACTIVE_DEVICE_AGE_MS = 7.776e+9; // 90 days
|
||||
export const INACTIVE_DEVICE_AGE_DAYS = INACTIVE_DEVICE_AGE_MS / MS_DAY;
|
||||
|
||||
export type FilterVariation = DeviceSecurityVariation.Verified
|
||||
| DeviceSecurityVariation.Inactive
|
||||
| DeviceSecurityVariation.Unverified;
|
||||
|
||||
export const isDeviceInactive: DeviceFilterCondition = device =>
|
||||
!!device.last_seen_ts && device.last_seen_ts < Date.now() - INACTIVE_DEVICE_AGE_MS;
|
||||
|
||||
const filters: Record<DeviceSecurityVariation, DeviceFilterCondition> = {
|
||||
const filters: Record<FilterVariation, DeviceFilterCondition> = {
|
||||
[DeviceSecurityVariation.Verified]: device => !!device.isVerified,
|
||||
[DeviceSecurityVariation.Unverified]: device => !device.isVerified,
|
||||
[DeviceSecurityVariation.Inactive]: isDeviceInactive,
|
||||
|
@ -33,7 +37,7 @@ const filters: Record<DeviceSecurityVariation, DeviceFilterCondition> = {
|
|||
|
||||
export const filterDevicesBySecurityRecommendation = (
|
||||
devices: ExtendedDevice[],
|
||||
securityVariations: DeviceSecurityVariation[],
|
||||
securityVariations: FilterVariation[],
|
||||
) => {
|
||||
const activeFilters = securityVariations.map(variation => filters[variation]);
|
||||
if (!activeFilters.length) {
|
||||
|
|
|
@ -32,4 +32,7 @@ export enum DeviceSecurityVariation {
|
|||
Verified = 'Verified',
|
||||
Unverified = 'Unverified',
|
||||
Inactive = 'Inactive',
|
||||
// sessions that do not support encryption
|
||||
// eg a session that logged in via api to get an access token
|
||||
Unverifiable = 'Unverifiable'
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ import { useOwnDevices } from '../../devices/useOwnDevices';
|
|||
import { FilteredDeviceList } from '../../devices/FilteredDeviceList';
|
||||
import CurrentDeviceSection from '../../devices/CurrentDeviceSection';
|
||||
import SecurityRecommendations from '../../devices/SecurityRecommendations';
|
||||
import { DeviceSecurityVariation, ExtendedDevice } from '../../devices/types';
|
||||
import { ExtendedDevice } from '../../devices/types';
|
||||
import { deleteDevicesWithInteractiveAuth } from '../../devices/deleteDevices';
|
||||
import SettingsTab from '../SettingsTab';
|
||||
import LoginWithQRSection from '../../devices/LoginWithQRSection';
|
||||
|
@ -37,6 +37,7 @@ import LoginWithQR, { Mode } from '../../../auth/LoginWithQR';
|
|||
import SettingsStore from '../../../../../settings/SettingsStore';
|
||||
import { useAsyncMemo } from '../../../../../hooks/useAsyncMemo';
|
||||
import QuestionDialog from '../../../dialogs/QuestionDialog';
|
||||
import { FilterVariation } from '../../devices/filter';
|
||||
|
||||
const confirmSignOut = async (sessionsToSignOutCount: number): Promise<boolean> => {
|
||||
const { finished } = Modal.createDialog(QuestionDialog, {
|
||||
|
@ -123,7 +124,7 @@ const SessionManagerTab: React.FC = () => {
|
|||
setPushNotifications,
|
||||
supportsMSC3881,
|
||||
} = useOwnDevices();
|
||||
const [filter, setFilter] = useState<DeviceSecurityVariation>();
|
||||
const [filter, setFilter] = useState<FilterVariation>();
|
||||
const [expandedDeviceIds, setExpandedDeviceIds] = useState<ExtendedDevice['device_id'][]>([]);
|
||||
const [selectedDeviceIds, setSelectedDeviceIds] = useState<ExtendedDevice['device_id'][]>([]);
|
||||
const filteredDeviceListRef = useRef<HTMLDivElement>(null);
|
||||
|
@ -142,7 +143,7 @@ const SessionManagerTab: React.FC = () => {
|
|||
}
|
||||
};
|
||||
|
||||
const onGoToFilteredList = (filter: DeviceSecurityVariation) => {
|
||||
const onGoToFilteredList = (filter: FilterVariation) => {
|
||||
setFilter(filter);
|
||||
clearTimeout(scrollIntoViewTimeoutRef.current);
|
||||
// wait a tick for the filtered section to rerender with different height
|
||||
|
|
|
@ -1801,6 +1801,10 @@
|
|||
"Unverified sessions": "Unverified sessions",
|
||||
"Unverified sessions are sessions that have logged in with your credentials but have not been cross-verified.": "Unverified sessions are sessions that have logged in with your credentials but have not been cross-verified.",
|
||||
"You should make especially certain that you recognise these sessions as they could represent an unauthorised use of your account.": "You should make especially certain that you recognise these sessions as they could represent an unauthorised use of your account.",
|
||||
"Unverified session": "Unverified session",
|
||||
"This session doesn't support encryption, so it can't be verified.": "This session doesn't support encryption, so it can't be verified.",
|
||||
"You won't be able to participate in rooms where encryption is enabled when using this session.": "You won't be able to participate in rooms where encryption is enabled when using this session.",
|
||||
"For best security and privacy, it is recommended to use Matrix clients that support encryption.": "For best security and privacy, it is recommended to use Matrix clients that support encryption.",
|
||||
"Inactive sessions": "Inactive sessions",
|
||||
"Inactive sessions are sessions you have not used in some time, but they continue to receive encryption keys.": "Inactive sessions are sessions you have not used in some time, but they continue to receive encryption keys.",
|
||||
"Removing inactive sessions improves security and performance, and makes it easier for you to identify if a new session is suspicious.": "Removing inactive sessions improves security and performance, and makes it easier for you to identify if a new session is suspicious.",
|
||||
|
@ -1813,7 +1817,7 @@
|
|||
"Unknown session type": "Unknown session type",
|
||||
"Verified session": "Verified session",
|
||||
"This session is ready for secure messaging.": "This session is ready for secure messaging.",
|
||||
"Unverified session": "Unverified session",
|
||||
"This session doesn't support encryption and thus can't be verified.": "This session doesn't support encryption and thus can't be verified.",
|
||||
"Verify or sign out from this session for best security and reliability.": "Verify or sign out from this session for best security and reliability.",
|
||||
"Verify session": "Verify session",
|
||||
"For best security, sign out from any session that you don't recognize or use anymore.": "For best security, sign out from any session that you don't recognize or use anymore.",
|
||||
|
|
|
@ -255,12 +255,23 @@ describe('<SessionManagerTab />', () => {
|
|||
});
|
||||
|
||||
it('sets device verification status correctly', async () => {
|
||||
mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] });
|
||||
mockClient.getDevices.mockResolvedValue({ devices:
|
||||
[alicesDevice, alicesMobileDevice, alicesOlderMobileDevice],
|
||||
});
|
||||
mockClient.getStoredDevice.mockImplementation((_userId, deviceId) => new DeviceInfo(deviceId));
|
||||
mockCrossSigningInfo.checkDeviceTrust
|
||||
// alices device is trusted
|
||||
.mockReturnValueOnce(new DeviceTrustLevel(true, true, false, false))
|
||||
// alices mobile device is not
|
||||
.mockReturnValueOnce(new DeviceTrustLevel(false, false, false, false));
|
||||
.mockImplementation((_userId, { deviceId }) => {
|
||||
// alices device is trusted
|
||||
if (deviceId === alicesDevice.device_id) {
|
||||
return new DeviceTrustLevel(true, true, false, false);
|
||||
}
|
||||
// alices mobile device is not
|
||||
if (deviceId === alicesMobileDevice.device_id) {
|
||||
return new DeviceTrustLevel(false, false, false, false);
|
||||
}
|
||||
// alicesOlderMobileDevice does not support encryption
|
||||
throw new Error('encryption not supported');
|
||||
});
|
||||
|
||||
const { getByTestId } = render(getComponent());
|
||||
|
||||
|
@ -268,8 +279,20 @@ describe('<SessionManagerTab />', () => {
|
|||
await flushPromises();
|
||||
});
|
||||
|
||||
expect(mockCrossSigningInfo.checkDeviceTrust).toHaveBeenCalledTimes(2);
|
||||
expect(getByTestId(`device-tile-${alicesDevice.device_id}`)).toMatchSnapshot();
|
||||
expect(mockCrossSigningInfo.checkDeviceTrust).toHaveBeenCalledTimes(3);
|
||||
expect(
|
||||
getByTestId(`device-tile-${alicesDevice.device_id}`)
|
||||
.querySelector('[aria-label="Verified"]'),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
getByTestId(`device-tile-${alicesMobileDevice.device_id}`)
|
||||
.querySelector('[aria-label="Unverified"]'),
|
||||
).toBeTruthy();
|
||||
// sessions that dont support encryption use unverified badge
|
||||
expect(
|
||||
getByTestId(`device-tile-${alicesOlderMobileDevice.device_id}`)
|
||||
.querySelector('[aria-label="Unverified"]'),
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('extends device with client information when available', async () => {
|
||||
|
@ -489,7 +512,7 @@ describe('<SessionManagerTab />', () => {
|
|||
if (deviceId === alicesDevice.device_id) {
|
||||
return new DeviceTrustLevel(true, true, false, false);
|
||||
}
|
||||
throw new Error('everything else unverified');
|
||||
return new DeviceTrustLevel(false, false, false, false);
|
||||
});
|
||||
|
||||
const { getByTestId } = render(getComponent());
|
||||
|
@ -507,6 +530,38 @@ describe('<SessionManagerTab />', () => {
|
|||
expect(modalSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not allow device verification on session that do not support encryption', async () => {
|
||||
mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] });
|
||||
mockClient.getStoredDevice.mockImplementation((_userId, deviceId) => new DeviceInfo(deviceId));
|
||||
mockCrossSigningInfo.checkDeviceTrust
|
||||
.mockImplementation((_userId, { deviceId }) => {
|
||||
// current session verified = able to verify other sessions
|
||||
if (deviceId === alicesDevice.device_id) {
|
||||
return new DeviceTrustLevel(true, true, false, false);
|
||||
}
|
||||
// but alicesMobileDevice doesn't support encryption
|
||||
throw new Error('encryption not supported');
|
||||
});
|
||||
|
||||
const {
|
||||
getByTestId,
|
||||
queryByTestId,
|
||||
} = render(getComponent());
|
||||
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
toggleDeviceDetails(getByTestId, alicesMobileDevice.device_id);
|
||||
|
||||
// no verify button
|
||||
expect(queryByTestId(`verification-status-button-${alicesMobileDevice.device_id}`)).toBeFalsy();
|
||||
expect(
|
||||
getByTestId(`device-detail-${alicesMobileDevice.device_id}`)
|
||||
.getElementsByClassName('mx_DeviceSecurityCard'),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('refreshes devices after verifying other device', async () => {
|
||||
const modalSpy = jest.spyOn(Modal, 'createDialog');
|
||||
|
||||
|
@ -518,7 +573,7 @@ describe('<SessionManagerTab />', () => {
|
|||
if (deviceId === alicesDevice.device_id) {
|
||||
return new DeviceTrustLevel(true, true, false, false);
|
||||
}
|
||||
throw new Error('everything else unverified');
|
||||
return new DeviceTrustLevel(false, false, false, false);
|
||||
});
|
||||
|
||||
const { getByTestId } = render(getComponent());
|
||||
|
|
|
@ -1,5 +1,43 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<SessionManagerTab /> Device verification does not allow device verification on session that do not support encryption 1`] = `
|
||||
HTMLCollection [
|
||||
<div
|
||||
class="mx_DeviceSecurityCard"
|
||||
>
|
||||
<div
|
||||
class="mx_DeviceSecurityCard_icon Unverified"
|
||||
>
|
||||
<div
|
||||
height="16"
|
||||
width="16"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="mx_DeviceSecurityCard_content"
|
||||
>
|
||||
<p
|
||||
class="mx_DeviceSecurityCard_heading"
|
||||
>
|
||||
Unverified session
|
||||
</p>
|
||||
<p
|
||||
class="mx_DeviceSecurityCard_description"
|
||||
>
|
||||
This session doesn't support encryption and thus can't be verified.
|
||||
<div
|
||||
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Learn more
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`<SessionManagerTab /> Sign out Signs out of current device 1`] = `
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_danger_inline"
|
||||
|
@ -345,68 +383,3 @@ exports[`<SessionManagerTab /> goes to filtered list from security recommendatio
|
|||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<SessionManagerTab /> sets device verification status correctly 1`] = `
|
||||
<div
|
||||
class="mx_DeviceTile mx_DeviceTile_interactive"
|
||||
data-testid="device-tile-alices_device"
|
||||
>
|
||||
<div
|
||||
class="mx_DeviceTypeIcon"
|
||||
>
|
||||
<div
|
||||
class="mx_DeviceTypeIcon_deviceIconWrapper"
|
||||
>
|
||||
<div
|
||||
aria-label="Unknown session type"
|
||||
class="mx_DeviceTypeIcon_deviceIcon"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Verified"
|
||||
class="mx_DeviceTypeIcon_verificationIcon verified"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="mx_DeviceTile_info"
|
||||
>
|
||||
<h4
|
||||
class="mx_Heading_h4"
|
||||
>
|
||||
Alices device
|
||||
</h4>
|
||||
<div
|
||||
class="mx_DeviceTile_metadata"
|
||||
>
|
||||
<span
|
||||
data-testid="device-metadata-isVerified"
|
||||
>
|
||||
Verified
|
||||
</span>
|
||||
·
|
||||
<span
|
||||
data-testid="device-metadata-deviceId"
|
||||
>
|
||||
alices_device
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_DeviceTile_actions"
|
||||
>
|
||||
<div
|
||||
aria-label="Show details"
|
||||
class="mx_AccessibleButton mx_DeviceExpandDetailsButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_icon"
|
||||
data-testid="current-session-toggle-details"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_DeviceExpandDetailsButton_icon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
Loading…
Reference in New Issue