Replace uses of checkDeviceTrust
with getDeviceVerificationStatus
(#10663)
matrix-org/matrix-js-sdk#3287 and matrix-org/matrix-js-sdk#3303 added a new API called getDeviceVerificationStatus. Let's use it.
This commit is contained in:
parent
aa8c0f5cc7
commit
d7bb8043ea
22 changed files with 286 additions and 161 deletions
|
@ -310,7 +310,11 @@ export default class DeviceListener {
|
||||||
const newUnverifiedDeviceIds = new Set<string>();
|
const newUnverifiedDeviceIds = new Set<string>();
|
||||||
|
|
||||||
const isCurrentDeviceTrusted =
|
const isCurrentDeviceTrusted =
|
||||||
crossSigningReady && (await cli.checkDeviceTrust(cli.getUserId()!, cli.deviceId!).isCrossSigningVerified());
|
crossSigningReady &&
|
||||||
|
Boolean(
|
||||||
|
(await cli.getCrypto()?.getDeviceVerificationStatus(cli.getUserId()!, cli.deviceId!))
|
||||||
|
?.crossSigningVerified,
|
||||||
|
);
|
||||||
|
|
||||||
// as long as cross-signing isn't ready,
|
// as long as cross-signing isn't ready,
|
||||||
// you can't see or dismiss any device toasts
|
// you can't see or dismiss any device toasts
|
||||||
|
@ -319,8 +323,10 @@ export default class DeviceListener {
|
||||||
for (const device of devices) {
|
for (const device of devices) {
|
||||||
if (device.deviceId === cli.deviceId) continue;
|
if (device.deviceId === cli.deviceId) continue;
|
||||||
|
|
||||||
const deviceTrust = await cli.checkDeviceTrust(cli.getUserId()!, device.deviceId!);
|
const deviceTrust = await cli
|
||||||
if (!deviceTrust.isCrossSigningVerified() && !this.dismissed.has(device.deviceId)) {
|
.getCrypto()!
|
||||||
|
.getDeviceVerificationStatus(cli.getUserId()!, device.deviceId!);
|
||||||
|
if (!deviceTrust?.crossSigningVerified && !this.dismissed.has(device.deviceId)) {
|
||||||
if (this.ourDeviceIdsAtStart?.has(device.deviceId)) {
|
if (this.ourDeviceIdsAtStart?.has(device.deviceId)) {
|
||||||
oldUnverifiedDeviceIds.add(device.deviceId);
|
oldUnverifiedDeviceIds.add(device.deviceId);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1073,9 +1073,9 @@ export const Commands = [
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const deviceTrust = await cli.checkDeviceTrust(userId, deviceId);
|
const deviceTrust = await cli.getCrypto()?.getDeviceVerificationStatus(userId, deviceId);
|
||||||
|
|
||||||
if (deviceTrust.isVerified()) {
|
if (deviceTrust?.isVerified()) {
|
||||||
if (device.getFingerprint() === fingerprint) {
|
if (device.getFingerprint() === fingerprint) {
|
||||||
throw new UserFriendlyError("Session already verified!");
|
throw new UserFriendlyError("Session already verified!");
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -79,6 +79,7 @@ import PosthogTrackers from "../../../PosthogTrackers";
|
||||||
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||||
import { DirectoryMember, startDmOnFirstMessage } from "../../../utils/direct-messages";
|
import { DirectoryMember, startDmOnFirstMessage } from "../../../utils/direct-messages";
|
||||||
import { SdkContextClass } from "../../../contexts/SDKContext";
|
import { SdkContextClass } from "../../../contexts/SDKContext";
|
||||||
|
import { asyncSome } from "../../../utils/arrays";
|
||||||
|
|
||||||
export interface IDevice extends DeviceInfo {
|
export interface IDevice extends DeviceInfo {
|
||||||
ambiguous?: boolean;
|
ambiguous?: boolean;
|
||||||
|
@ -101,22 +102,22 @@ export const disambiguateDevices = (devices: IDevice[]): void => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getE2EStatus = (cli: MatrixClient, userId: string, devices: IDevice[]): E2EStatus => {
|
export const getE2EStatus = async (cli: MatrixClient, userId: string, devices: IDevice[]): Promise<E2EStatus> => {
|
||||||
const isMe = userId === cli.getUserId();
|
const isMe = userId === cli.getUserId();
|
||||||
const userTrust = cli.checkUserTrust(userId);
|
const userTrust = cli.checkUserTrust(userId);
|
||||||
if (!userTrust.isCrossSigningVerified()) {
|
if (!userTrust.isCrossSigningVerified()) {
|
||||||
return userTrust.wasCrossSigningVerified() ? E2EStatus.Warning : E2EStatus.Normal;
|
return userTrust.wasCrossSigningVerified() ? E2EStatus.Warning : E2EStatus.Normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
const anyDeviceUnverified = devices.some((device) => {
|
const anyDeviceUnverified = await asyncSome(devices, async (device) => {
|
||||||
const { deviceId } = device;
|
const { deviceId } = device;
|
||||||
// For your own devices, we use the stricter check of cross-signing
|
// For your own devices, we use the stricter check of cross-signing
|
||||||
// verification to encourage everyone to trust their own devices via
|
// verification to encourage everyone to trust their own devices via
|
||||||
// cross-signing so that other users can then safely trust you.
|
// cross-signing so that other users can then safely trust you.
|
||||||
// For other people's devices, the more general verified check that
|
// For other people's devices, the more general verified check that
|
||||||
// includes locally verified devices can be used.
|
// includes locally verified devices can be used.
|
||||||
const deviceTrust = cli.checkDeviceTrust(userId, deviceId);
|
const deviceTrust = await cli.getCrypto()?.getDeviceVerificationStatus(userId, deviceId);
|
||||||
return isMe ? !deviceTrust.isCrossSigningVerified() : !deviceTrust.isVerified();
|
return isMe ? !deviceTrust?.crossSigningVerified : !deviceTrust?.isVerified();
|
||||||
});
|
});
|
||||||
return anyDeviceUnverified ? E2EStatus.Warning : E2EStatus.Verified;
|
return anyDeviceUnverified ? E2EStatus.Warning : E2EStatus.Verified;
|
||||||
};
|
};
|
||||||
|
@ -161,14 +162,20 @@ function useHasCrossSigningKeys(
|
||||||
export function DeviceItem({ userId, device }: { userId: string; device: IDevice }): JSX.Element {
|
export function DeviceItem({ userId, device }: { userId: string; device: IDevice }): JSX.Element {
|
||||||
const cli = useContext(MatrixClientContext);
|
const cli = useContext(MatrixClientContext);
|
||||||
const isMe = userId === cli.getUserId();
|
const isMe = userId === cli.getUserId();
|
||||||
const deviceTrust = cli.checkDeviceTrust(userId, device.deviceId);
|
|
||||||
const userTrust = cli.checkUserTrust(userId);
|
const userTrust = cli.checkUserTrust(userId);
|
||||||
// For your own devices, we use the stricter check of cross-signing
|
|
||||||
// verification to encourage everyone to trust their own devices via
|
/** is the device verified? */
|
||||||
// cross-signing so that other users can then safely trust you.
|
const isVerified = useAsyncMemo(async () => {
|
||||||
// For other people's devices, the more general verified check that
|
const deviceTrust = await cli.getCrypto()?.getDeviceVerificationStatus(userId, device.deviceId);
|
||||||
// includes locally verified devices can be used.
|
if (!deviceTrust) return false;
|
||||||
const isVerified = isMe ? deviceTrust.isCrossSigningVerified() : deviceTrust.isVerified();
|
|
||||||
|
// For your own devices, we use the stricter check of cross-signing
|
||||||
|
// verification to encourage everyone to trust their own devices via
|
||||||
|
// cross-signing so that other users can then safely trust you.
|
||||||
|
// For other people's devices, the more general verified check that
|
||||||
|
// includes locally verified devices can be used.
|
||||||
|
return isMe ? deviceTrust.crossSigningVerified : deviceTrust.isVerified();
|
||||||
|
}, [cli, userId, device]);
|
||||||
|
|
||||||
const classes = classNames("mx_UserInfo_device", {
|
const classes = classNames("mx_UserInfo_device", {
|
||||||
mx_UserInfo_device_verified: isVerified,
|
mx_UserInfo_device_verified: isVerified,
|
||||||
|
@ -199,7 +206,10 @@ export function DeviceItem({ userId, device }: { userId: string; device: IDevice
|
||||||
let trustedLabel: string | undefined;
|
let trustedLabel: string | undefined;
|
||||||
if (userTrust.isVerified()) trustedLabel = isVerified ? _t("Trusted") : _t("Not trusted");
|
if (userTrust.isVerified()) trustedLabel = isVerified ? _t("Trusted") : _t("Not trusted");
|
||||||
|
|
||||||
if (isVerified) {
|
if (isVerified === undefined) {
|
||||||
|
// we're still deciding if the device is verified
|
||||||
|
return <div className={classes} title={device.deviceId} />;
|
||||||
|
} else if (isVerified) {
|
||||||
return (
|
return (
|
||||||
<div className={classes} title={device.deviceId}>
|
<div className={classes} title={device.deviceId}>
|
||||||
<div className={iconClasses} />
|
<div className={iconClasses} />
|
||||||
|
@ -232,15 +242,17 @@ function DevicesSection({
|
||||||
|
|
||||||
const [isExpanded, setExpanded] = useState(false);
|
const [isExpanded, setExpanded] = useState(false);
|
||||||
|
|
||||||
if (loading) {
|
const deviceTrusts = useAsyncMemo(() => {
|
||||||
|
const cryptoApi = cli.getCrypto();
|
||||||
|
if (!cryptoApi) return Promise.resolve(undefined);
|
||||||
|
return Promise.all(devices.map((d) => cryptoApi.getDeviceVerificationStatus(userId, d.deviceId)));
|
||||||
|
}, [cli, userId, devices]);
|
||||||
|
|
||||||
|
if (loading || deviceTrusts === undefined) {
|
||||||
// still loading
|
// still loading
|
||||||
return <Spinner />;
|
return <Spinner />;
|
||||||
}
|
}
|
||||||
if (devices === null) {
|
|
||||||
return <p>{_t("Unable to load session list")}</p>;
|
|
||||||
}
|
|
||||||
const isMe = userId === cli.getUserId();
|
const isMe = userId === cli.getUserId();
|
||||||
const deviceTrusts = devices.map((d) => cli.checkDeviceTrust(userId, d.deviceId));
|
|
||||||
|
|
||||||
let expandSectionDevices: IDevice[] = [];
|
let expandSectionDevices: IDevice[] = [];
|
||||||
const unverifiedDevices: IDevice[] = [];
|
const unverifiedDevices: IDevice[] = [];
|
||||||
|
@ -258,7 +270,7 @@ function DevicesSection({
|
||||||
// cross-signing so that other users can then safely trust you.
|
// cross-signing so that other users can then safely trust you.
|
||||||
// For other people's devices, the more general verified check that
|
// For other people's devices, the more general verified check that
|
||||||
// includes locally verified devices can be used.
|
// includes locally verified devices can be used.
|
||||||
const isVerified = isMe ? deviceTrust.isCrossSigningVerified() : deviceTrust.isVerified();
|
const isVerified = deviceTrust && (isMe ? deviceTrust.crossSigningVerified : deviceTrust.isVerified());
|
||||||
|
|
||||||
if (isVerified) {
|
if (isVerified) {
|
||||||
expandSectionDevices.push(device);
|
expandSectionDevices.push(device);
|
||||||
|
@ -1611,10 +1623,12 @@ const UserInfo: React.FC<IProps> = ({ user, room, onClose, phase = RightPanelPha
|
||||||
const isRoomEncrypted = useIsEncrypted(cli, room);
|
const isRoomEncrypted = useIsEncrypted(cli, room);
|
||||||
const devices = useDevices(user.userId) ?? [];
|
const devices = useDevices(user.userId) ?? [];
|
||||||
|
|
||||||
let e2eStatus: E2EStatus | undefined;
|
const e2eStatus = useAsyncMemo(async () => {
|
||||||
if (isRoomEncrypted && devices) {
|
if (!isRoomEncrypted || !devices) {
|
||||||
e2eStatus = getE2EStatus(cli, user.userId, devices);
|
return undefined;
|
||||||
}
|
}
|
||||||
|
return await getE2EStatus(cli, user.userId, devices);
|
||||||
|
}, [cli, isRoomEncrypted, user.userId, devices]);
|
||||||
|
|
||||||
const classes = ["mx_UserInfo"];
|
const classes = ["mx_UserInfo"];
|
||||||
|
|
||||||
|
|
|
@ -265,6 +265,8 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||||
public static contextType = RoomContext;
|
public static contextType = RoomContext;
|
||||||
public context!: React.ContextType<typeof RoomContext>;
|
public context!: React.ContextType<typeof RoomContext>;
|
||||||
|
|
||||||
|
private unmounted = false;
|
||||||
|
|
||||||
public constructor(props: EventTileProps, context: React.ContextType<typeof MatrixClientContext>) {
|
public constructor(props: EventTileProps, context: React.ContextType<typeof MatrixClientContext>) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
|
@ -420,6 +422,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||||
this.props.mxEvent.removeListener(MatrixEventEvent.RelationsCreated, this.onReactionsCreated);
|
this.props.mxEvent.removeListener(MatrixEventEvent.RelationsCreated, this.onReactionsCreated);
|
||||||
}
|
}
|
||||||
this.props.mxEvent.off(ThreadEvent.Update, this.updateThread);
|
this.props.mxEvent.off(ThreadEvent.Update, this.updateThread);
|
||||||
|
this.unmounted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidUpdate(prevProps: Readonly<EventTileProps>, prevState: Readonly<IState>): void {
|
public componentDidUpdate(prevProps: Readonly<EventTileProps>, prevState: Readonly<IState>): void {
|
||||||
|
@ -561,7 +564,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||||
this.verifyEvent();
|
this.verifyEvent();
|
||||||
};
|
};
|
||||||
|
|
||||||
private verifyEvent(): void {
|
private async verifyEvent(): Promise<void> {
|
||||||
// if the event was edited, show the verification info for the edit, not
|
// if the event was edited, show the verification info for the edit, not
|
||||||
// the original
|
// the original
|
||||||
const mxEvent = this.props.mxEvent.replacingEvent() ?? this.props.mxEvent;
|
const mxEvent = this.props.mxEvent.replacingEvent() ?? this.props.mxEvent;
|
||||||
|
@ -590,7 +593,14 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||||
}
|
}
|
||||||
|
|
||||||
const eventSenderTrust =
|
const eventSenderTrust =
|
||||||
encryptionInfo.sender && MatrixClientPeg.get().checkDeviceTrust(senderId, encryptionInfo.sender.deviceId);
|
senderId &&
|
||||||
|
encryptionInfo.sender &&
|
||||||
|
(await MatrixClientPeg.get()
|
||||||
|
.getCrypto()
|
||||||
|
?.getDeviceVerificationStatus(senderId, encryptionInfo.sender.deviceId));
|
||||||
|
|
||||||
|
if (this.unmounted) return;
|
||||||
|
|
||||||
if (!eventSenderTrust) {
|
if (!eventSenderTrust) {
|
||||||
this.setState({ verified: E2EState.Unknown });
|
this.setState({ verified: E2EState.Unknown });
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -33,6 +33,7 @@ import MemberAvatar from "./../avatars/MemberAvatar";
|
||||||
import DisambiguatedProfile from "../messages/DisambiguatedProfile";
|
import DisambiguatedProfile from "../messages/DisambiguatedProfile";
|
||||||
import UserIdentifierCustomisations from "../../../customisations/UserIdentifier";
|
import UserIdentifierCustomisations from "../../../customisations/UserIdentifier";
|
||||||
import { E2EState } from "./E2EIcon";
|
import { E2EState } from "./E2EIcon";
|
||||||
|
import { asyncSome } from "../../../utils/arrays";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
member: RoomMember;
|
member: RoomMember;
|
||||||
|
@ -127,15 +128,15 @@ export default class MemberTile extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const devices = cli.getStoredDevicesForUser(userId);
|
const devices = cli.getStoredDevicesForUser(userId);
|
||||||
const anyDeviceUnverified = devices.some((device) => {
|
const anyDeviceUnverified = await asyncSome(devices, async (device) => {
|
||||||
const { deviceId } = device;
|
const { deviceId } = device;
|
||||||
// For your own devices, we use the stricter check of cross-signing
|
// For your own devices, we use the stricter check of cross-signing
|
||||||
// verification to encourage everyone to trust their own devices via
|
// verification to encourage everyone to trust their own devices via
|
||||||
// cross-signing so that other users can then safely trust you.
|
// cross-signing so that other users can then safely trust you.
|
||||||
// For other people's devices, the more general verified check that
|
// For other people's devices, the more general verified check that
|
||||||
// includes locally verified devices can be used.
|
// includes locally verified devices can be used.
|
||||||
const deviceTrust = cli.checkDeviceTrust(userId, deviceId);
|
const deviceTrust = await cli.getCrypto()?.getDeviceVerificationStatus(userId, deviceId);
|
||||||
return isMe ? !deviceTrust.isCrossSigningVerified() : !deviceTrust.isVerified();
|
return !deviceTrust || (isMe ? !deviceTrust.crossSigningVerified : !deviceTrust.isVerified());
|
||||||
});
|
});
|
||||||
this.setState({
|
this.setState({
|
||||||
e2eStatus: anyDeviceUnverified ? E2EState.Warning : E2EState.Verified,
|
e2eStatus: anyDeviceUnverified ? E2EState.Warning : E2EState.Verified,
|
||||||
|
|
|
@ -26,14 +26,15 @@ import Spinner from "../elements/Spinner";
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import { deleteDevicesWithInteractiveAuth } from "./devices/deleteDevices";
|
import { deleteDevicesWithInteractiveAuth } from "./devices/deleteDevices";
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import { isDeviceVerified } from "../../../utils/device/isDeviceVerified";
|
import { fetchExtendedDeviceInformation } from "./devices/useOwnDevices";
|
||||||
|
import { DevicesDictionary, ExtendedDevice } from "./devices/types";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
devices: IMyDevice[];
|
devices?: DevicesDictionary;
|
||||||
deviceLoadError?: string;
|
deviceLoadError?: string;
|
||||||
selectedDevices: string[];
|
selectedDevices: string[];
|
||||||
deleting?: boolean;
|
deleting?: boolean;
|
||||||
|
@ -47,7 +48,6 @@ export default class DevicesPanel extends React.Component<IProps, IState> {
|
||||||
public constructor(props: IProps) {
|
public constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
devices: [],
|
|
||||||
selectedDevices: [],
|
selectedDevices: [],
|
||||||
};
|
};
|
||||||
this.loadDevices = this.loadDevices.bind(this);
|
this.loadDevices = this.loadDevices.bind(this);
|
||||||
|
@ -70,18 +70,16 @@ export default class DevicesPanel extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
private loadDevices(): void {
|
private loadDevices(): void {
|
||||||
const cli = this.context;
|
const cli = this.context;
|
||||||
cli.getDevices().then(
|
fetchExtendedDeviceInformation(cli).then(
|
||||||
(resp) => {
|
(devices) => {
|
||||||
if (this.unmounted) {
|
if (this.unmounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState((state, props) => {
|
this.setState((state, props) => {
|
||||||
const deviceIds = resp.devices.map((device) => device.device_id);
|
|
||||||
const selectedDevices = state.selectedDevices.filter((deviceId) => deviceIds.includes(deviceId));
|
|
||||||
return {
|
return {
|
||||||
devices: resp.devices || [],
|
devices: devices,
|
||||||
selectedDevices,
|
selectedDevices: state.selectedDevices.filter((deviceId) => devices.hasOwnProperty(deviceId)),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -119,10 +117,6 @@ export default class DevicesPanel extends React.Component<IProps, IState> {
|
||||||
return idA < idB ? -1 : idA > idB ? 1 : 0;
|
return idA < idB ? -1 : idA > idB ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private isDeviceVerified(device: IMyDevice): boolean | null {
|
|
||||||
return isDeviceVerified(this.context, device.device_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
private onDeviceSelectionToggled = (device: IMyDevice): void => {
|
private onDeviceSelectionToggled = (device: IMyDevice): void => {
|
||||||
if (this.unmounted) {
|
if (this.unmounted) {
|
||||||
return;
|
return;
|
||||||
|
@ -205,15 +199,15 @@ export default class DevicesPanel extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private renderDevice = (device: IMyDevice): JSX.Element => {
|
private renderDevice = (device: ExtendedDevice): JSX.Element => {
|
||||||
const myDeviceId = this.context.getDeviceId();
|
const myDeviceId = this.context.getDeviceId()!;
|
||||||
const myDevice = this.state.devices.find((device) => device.device_id === myDeviceId);
|
const myDevice = this.state.devices?.[myDeviceId];
|
||||||
|
|
||||||
const isOwnDevice = device.device_id === myDeviceId;
|
const isOwnDevice = device.device_id === myDeviceId;
|
||||||
|
|
||||||
// If our own device is unverified, it can't verify other
|
// If our own device is unverified, it can't verify other
|
||||||
// devices, it can only request verification for itself
|
// devices, it can only request verification for itself
|
||||||
const canBeVerified = (myDevice && this.isDeviceVerified(myDevice)) || isOwnDevice;
|
const canBeVerified = (myDevice && myDevice.isVerified) || isOwnDevice;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DevicesPanelEntry
|
<DevicesPanelEntry
|
||||||
|
@ -221,7 +215,7 @@ export default class DevicesPanel extends React.Component<IProps, IState> {
|
||||||
device={device}
|
device={device}
|
||||||
selected={this.state.selectedDevices.includes(device.device_id)}
|
selected={this.state.selectedDevices.includes(device.device_id)}
|
||||||
isOwnDevice={isOwnDevice}
|
isOwnDevice={isOwnDevice}
|
||||||
verified={this.isDeviceVerified(device)}
|
verified={device.isVerified}
|
||||||
canBeVerified={canBeVerified}
|
canBeVerified={canBeVerified}
|
||||||
onDeviceChange={this.loadDevices}
|
onDeviceChange={this.loadDevices}
|
||||||
onDeviceToggled={this.onDeviceSelectionToggled}
|
onDeviceToggled={this.onDeviceSelectionToggled}
|
||||||
|
@ -242,21 +236,21 @@ export default class DevicesPanel extends React.Component<IProps, IState> {
|
||||||
return <Spinner />;
|
return <Spinner />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const myDeviceId = this.context.getDeviceId();
|
const myDeviceId = this.context.getDeviceId()!;
|
||||||
const myDevice = devices.find((device) => device.device_id === myDeviceId);
|
const myDevice = devices[myDeviceId];
|
||||||
|
|
||||||
if (!myDevice) {
|
if (!myDevice) {
|
||||||
return loadError;
|
return loadError;
|
||||||
}
|
}
|
||||||
|
|
||||||
const otherDevices = devices.filter((device) => device.device_id !== myDeviceId);
|
const otherDevices = Object.values(devices).filter((device) => device.device_id !== myDeviceId);
|
||||||
otherDevices.sort(this.deviceCompare);
|
otherDevices.sort(this.deviceCompare);
|
||||||
|
|
||||||
const verifiedDevices: IMyDevice[] = [];
|
const verifiedDevices: ExtendedDevice[] = [];
|
||||||
const unverifiedDevices: IMyDevice[] = [];
|
const unverifiedDevices: ExtendedDevice[] = [];
|
||||||
const nonCryptoDevices: IMyDevice[] = [];
|
const nonCryptoDevices: ExtendedDevice[] = [];
|
||||||
for (const device of otherDevices) {
|
for (const device of otherDevices) {
|
||||||
const verified = this.isDeviceVerified(device);
|
const verified = device.isVerified;
|
||||||
if (verified === true) {
|
if (verified === true) {
|
||||||
verifiedDevices.push(device);
|
verifiedDevices.push(device);
|
||||||
} else if (verified === false) {
|
} else if (verified === false) {
|
||||||
|
@ -266,7 +260,7 @@ export default class DevicesPanel extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const section = (trustIcon: JSX.Element, title: string, deviceList: IMyDevice[]): JSX.Element => {
|
const section = (trustIcon: JSX.Element, title: string, deviceList: ExtendedDevice[]): JSX.Element => {
|
||||||
if (deviceList.length === 0) {
|
if (deviceList.length === 0) {
|
||||||
return <React.Fragment />;
|
return <React.Fragment />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,24 +50,26 @@ const parseDeviceExtendedInformation = (matrixClient: MatrixClient, device: IMyD
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchDevicesWithVerification = async (matrixClient: MatrixClient): Promise<DevicesState["devices"]> => {
|
/**
|
||||||
|
* Fetch extended details of the user's own devices
|
||||||
|
*
|
||||||
|
* @param matrixClient - Matrix Client
|
||||||
|
* @returns A dictionary mapping from device ID to ExtendedDevice
|
||||||
|
*/
|
||||||
|
export async function fetchExtendedDeviceInformation(matrixClient: MatrixClient): Promise<DevicesDictionary> {
|
||||||
const { devices } = await matrixClient.getDevices();
|
const { devices } = await matrixClient.getDevices();
|
||||||
|
|
||||||
const devicesDict = devices.reduce(
|
const devicesDict: DevicesDictionary = {};
|
||||||
(acc, device: IMyDevice) => ({
|
for (const device of devices) {
|
||||||
...acc,
|
devicesDict[device.device_id] = {
|
||||||
[device.device_id]: {
|
...device,
|
||||||
...device,
|
isVerified: await isDeviceVerified(matrixClient, device.device_id),
|
||||||
isVerified: isDeviceVerified(matrixClient, device.device_id),
|
...parseDeviceExtendedInformation(matrixClient, device),
|
||||||
...parseDeviceExtendedInformation(matrixClient, device),
|
...parseUserAgent(device[UNSTABLE_MSC3852_LAST_SEEN_UA.name]),
|
||||||
...parseUserAgent(device[UNSTABLE_MSC3852_LAST_SEEN_UA.name]),
|
};
|
||||||
},
|
}
|
||||||
}),
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
|
|
||||||
return devicesDict;
|
return devicesDict;
|
||||||
};
|
}
|
||||||
|
|
||||||
export enum OwnDevicesError {
|
export enum OwnDevicesError {
|
||||||
Unsupported = "Unsupported",
|
Unsupported = "Unsupported",
|
||||||
|
@ -112,7 +114,7 @@ export const useOwnDevices = (): DevicesState => {
|
||||||
const refreshDevices = useCallback(async (): Promise<void> => {
|
const refreshDevices = useCallback(async (): Promise<void> => {
|
||||||
setIsLoadingDeviceList(true);
|
setIsLoadingDeviceList(true);
|
||||||
try {
|
try {
|
||||||
const devices = await fetchDevicesWithVerification(matrixClient);
|
const devices = await fetchExtendedDeviceInformation(matrixClient);
|
||||||
setDevices(devices);
|
setDevices(devices);
|
||||||
|
|
||||||
const { pushers } = await matrixClient.getPushers();
|
const { pushers } = await matrixClient.getPushers();
|
||||||
|
|
|
@ -2256,7 +2256,6 @@
|
||||||
"Room settings": "Room settings",
|
"Room settings": "Room settings",
|
||||||
"Trusted": "Trusted",
|
"Trusted": "Trusted",
|
||||||
"Not trusted": "Not trusted",
|
"Not trusted": "Not trusted",
|
||||||
"Unable to load session list": "Unable to load session list",
|
|
||||||
"%(count)s verified sessions|other": "%(count)s verified sessions",
|
"%(count)s verified sessions|other": "%(count)s verified sessions",
|
||||||
"%(count)s verified sessions|one": "1 verified session",
|
"%(count)s verified sessions|one": "1 verified session",
|
||||||
"Hide verified sessions": "Hide verified sessions",
|
"Hide verified sessions": "Hide verified sessions",
|
||||||
|
|
|
@ -109,14 +109,20 @@ export class SetupEncryptionStore extends EventEmitter {
|
||||||
const dehydratedDevice = await cli.getDehydratedDevice();
|
const dehydratedDevice = await cli.getDehydratedDevice();
|
||||||
const ownUserId = cli.getUserId()!;
|
const ownUserId = cli.getUserId()!;
|
||||||
const crossSigningInfo = cli.getStoredCrossSigningForUser(ownUserId);
|
const crossSigningInfo = cli.getStoredCrossSigningForUser(ownUserId);
|
||||||
this.hasDevicesToVerifyAgainst = cli
|
this.hasDevicesToVerifyAgainst = cli.getStoredDevicesForUser(ownUserId).some((device) => {
|
||||||
.getStoredDevicesForUser(ownUserId)
|
if (!device.getIdentityKey() || (dehydratedDevice && device.deviceId == dehydratedDevice?.device_id)) {
|
||||||
.some(
|
return false;
|
||||||
(device) =>
|
}
|
||||||
device.getIdentityKey() &&
|
// check if the device is signed by the cross-signing key stored for our user. Note that this is
|
||||||
(!dehydratedDevice || device.deviceId != dehydratedDevice.device_id) &&
|
// *different* to calling `cryptoApi.getDeviceVerificationStatus`, because even if we have stored
|
||||||
crossSigningInfo?.checkDeviceTrust(crossSigningInfo, device, false, true).isCrossSigningVerified(),
|
// a cross-signing key for our user, we don't necessarily trust it yet (In legacy Crypto, we have not
|
||||||
);
|
// yet imported it into `Crypto.crossSigningInfo`, which for maximal confusion is a different object to
|
||||||
|
// `Crypto.getStoredCrossSigningForUser(ownUserId)`).
|
||||||
|
//
|
||||||
|
// TODO: figure out wtf to to here for rust-crypto
|
||||||
|
const verificationStatus = crossSigningInfo?.checkDeviceTrust(crossSigningInfo, device, false, true);
|
||||||
|
return !!verificationStatus?.isCrossSigningVerified();
|
||||||
|
});
|
||||||
|
|
||||||
this.phase = Phase.Intro;
|
this.phase = Phase.Intro;
|
||||||
this.emit("update");
|
this.emit("update");
|
||||||
|
|
|
@ -48,7 +48,7 @@ export const showToast = async (deviceId: string): Promise<void> => {
|
||||||
const device = await cli.getDevice(deviceId);
|
const device = await cli.getDevice(deviceId);
|
||||||
const extendedDevice = {
|
const extendedDevice = {
|
||||||
...device,
|
...device,
|
||||||
isVerified: isDeviceVerified(cli, deviceId),
|
isVerified: await isDeviceVerified(cli, deviceId),
|
||||||
deviceType: DeviceType.Unknown,
|
deviceType: DeviceType.Unknown,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
|
||||||
import DMRoomMap from "./DMRoomMap";
|
import DMRoomMap from "./DMRoomMap";
|
||||||
|
import { asyncSome } from "./arrays";
|
||||||
|
|
||||||
export enum E2EStatus {
|
export enum E2EStatus {
|
||||||
Warning = "warning",
|
Warning = "warning",
|
||||||
|
@ -54,8 +55,9 @@ export async function shieldStatusForRoom(client: MatrixClient, room: Room): Pro
|
||||||
const targets = includeUser ? [...verified, client.getUserId()!] : verified;
|
const targets = includeUser ? [...verified, client.getUserId()!] : verified;
|
||||||
for (const userId of targets) {
|
for (const userId of targets) {
|
||||||
const devices = client.getStoredDevicesForUser(userId);
|
const devices = client.getStoredDevicesForUser(userId);
|
||||||
const anyDeviceNotVerified = devices.some(({ deviceId }) => {
|
const anyDeviceNotVerified = await asyncSome(devices, async ({ deviceId }) => {
|
||||||
return !client.checkDeviceTrust(userId, deviceId).isVerified();
|
const verificationStatus = await client.getCrypto()?.getDeviceVerificationStatus(userId, deviceId);
|
||||||
|
return !verificationStatus?.isVerified();
|
||||||
});
|
});
|
||||||
if (anyDeviceNotVerified) {
|
if (anyDeviceNotVerified) {
|
||||||
return E2EStatus.Warning;
|
return E2EStatus.Warning;
|
||||||
|
|
|
@ -324,6 +324,16 @@ export async function asyncEvery<T>(values: T[], predicate: (value: T) => Promis
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Async version of Array.some.
|
||||||
|
*/
|
||||||
|
export async function asyncSome<T>(values: T[], predicate: (value: T) => Promise<boolean>): Promise<boolean> {
|
||||||
|
for (const value of values) {
|
||||||
|
if (await predicate(value)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
export function filterBoolean<T>(values: Array<T | null | undefined>): T[] {
|
export function filterBoolean<T>(values: Array<T | null | undefined>): T[] {
|
||||||
return values.filter(Boolean) as T[];
|
return values.filter(Boolean) as T[];
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,10 +25,10 @@ import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||||
* @returns `true` if the device has been correctly cross-signed. `false` if the device is unknown or not correctly
|
* @returns `true` if the device has been correctly cross-signed. `false` if the device is unknown or not correctly
|
||||||
* cross-signed. `null` if there was an error fetching the device info.
|
* cross-signed. `null` if there was an error fetching the device info.
|
||||||
*/
|
*/
|
||||||
export const isDeviceVerified = (client: MatrixClient, deviceId: string): boolean | null => {
|
export const isDeviceVerified = async (client: MatrixClient, deviceId: string): Promise<boolean | null> => {
|
||||||
try {
|
try {
|
||||||
const trustLevel = client.checkDeviceTrust(client.getSafeUserId(), deviceId);
|
const trustLevel = await client.getCrypto()?.getDeviceVerificationStatus(client.getSafeUserId(), deviceId);
|
||||||
return trustLevel.isCrossSigningVerified();
|
return trustLevel?.crossSigningVerified ?? false;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Error getting device cross-signing info", e);
|
console.error("Error getting device cross-signing info", e);
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -15,10 +15,10 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Mocked, mocked } from "jest-mock";
|
import { Mocked, mocked } from "jest-mock";
|
||||||
import { MatrixEvent, Room, MatrixClient } from "matrix-js-sdk/src/matrix";
|
import { MatrixEvent, Room, MatrixClient, DeviceVerificationStatus, CryptoApi } from "matrix-js-sdk/src/matrix";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo";
|
import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo";
|
||||||
import { CrossSigningInfo, DeviceTrustLevel } from "matrix-js-sdk/src/crypto/CrossSigning";
|
import { CrossSigningInfo } from "matrix-js-sdk/src/crypto/CrossSigning";
|
||||||
import { CryptoEvent } from "matrix-js-sdk/src/crypto";
|
import { CryptoEvent } from "matrix-js-sdk/src/crypto";
|
||||||
import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup";
|
import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup";
|
||||||
|
|
||||||
|
@ -60,6 +60,7 @@ const flushPromises = async () => await new Promise(process.nextTick);
|
||||||
|
|
||||||
describe("DeviceListener", () => {
|
describe("DeviceListener", () => {
|
||||||
let mockClient: Mocked<MatrixClient> | undefined;
|
let mockClient: Mocked<MatrixClient> | undefined;
|
||||||
|
let mockCrypto: Mocked<CryptoApi> | undefined;
|
||||||
|
|
||||||
// spy on various toasts' hide and show functions
|
// spy on various toasts' hide and show functions
|
||||||
// easier than mocking
|
// easier than mocking
|
||||||
|
@ -75,6 +76,11 @@ describe("DeviceListener", () => {
|
||||||
mockPlatformPeg({
|
mockPlatformPeg({
|
||||||
getAppVersion: jest.fn().mockResolvedValue("1.2.3"),
|
getAppVersion: jest.fn().mockResolvedValue("1.2.3"),
|
||||||
});
|
});
|
||||||
|
mockCrypto = {
|
||||||
|
getDeviceVerificationStatus: jest.fn().mockResolvedValue({
|
||||||
|
crossSigningVerified: false,
|
||||||
|
}),
|
||||||
|
} as unknown as Mocked<CryptoApi>;
|
||||||
mockClient = getMockClientWithEventEmitter({
|
mockClient = getMockClientWithEventEmitter({
|
||||||
isGuest: jest.fn(),
|
isGuest: jest.fn(),
|
||||||
getUserId: jest.fn().mockReturnValue(userId),
|
getUserId: jest.fn().mockReturnValue(userId),
|
||||||
|
@ -97,7 +103,7 @@ describe("DeviceListener", () => {
|
||||||
setAccountData: jest.fn(),
|
setAccountData: jest.fn(),
|
||||||
getAccountData: jest.fn(),
|
getAccountData: jest.fn(),
|
||||||
deleteAccountData: jest.fn(),
|
deleteAccountData: jest.fn(),
|
||||||
checkDeviceTrust: jest.fn().mockReturnValue(new DeviceTrustLevel(false, false, false, false)),
|
getCrypto: jest.fn().mockReturnValue(mockCrypto),
|
||||||
});
|
});
|
||||||
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient);
|
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient);
|
||||||
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
|
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
|
||||||
|
@ -391,14 +397,14 @@ describe("DeviceListener", () => {
|
||||||
const device2 = new DeviceInfo("d2");
|
const device2 = new DeviceInfo("d2");
|
||||||
const device3 = new DeviceInfo("d3");
|
const device3 = new DeviceInfo("d3");
|
||||||
|
|
||||||
const deviceTrustVerified = new DeviceTrustLevel(true, false, false, false);
|
const deviceTrustVerified = new DeviceVerificationStatus({ crossSigningVerified: true });
|
||||||
const deviceTrustUnverified = new DeviceTrustLevel(false, false, false, false);
|
const deviceTrustUnverified = new DeviceVerificationStatus({});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockClient!.isCrossSigningReady.mockResolvedValue(true);
|
mockClient!.isCrossSigningReady.mockResolvedValue(true);
|
||||||
mockClient!.getStoredDevicesForUser.mockReturnValue([currentDevice, device2, device3]);
|
mockClient!.getStoredDevicesForUser.mockReturnValue([currentDevice, device2, device3]);
|
||||||
// all devices verified by default
|
// all devices verified by default
|
||||||
mockClient!.checkDeviceTrust.mockReturnValue(deviceTrustVerified);
|
mockCrypto!.getDeviceVerificationStatus.mockResolvedValue(deviceTrustVerified);
|
||||||
mockClient!.deviceId = currentDevice.deviceId;
|
mockClient!.deviceId = currentDevice.deviceId;
|
||||||
jest.spyOn(SettingsStore, "getValue").mockImplementation(
|
jest.spyOn(SettingsStore, "getValue").mockImplementation(
|
||||||
(settingName) => settingName === UIFeature.BulkUnverifiedSessionsReminder,
|
(settingName) => settingName === UIFeature.BulkUnverifiedSessionsReminder,
|
||||||
|
@ -423,7 +429,7 @@ describe("DeviceListener", () => {
|
||||||
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
|
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
|
||||||
// currentDevice, device2 are verified, device3 is unverified
|
// currentDevice, device2 are verified, device3 is unverified
|
||||||
// ie if reminder was enabled it should be shown
|
// ie if reminder was enabled it should be shown
|
||||||
mockClient!.checkDeviceTrust.mockImplementation((_userId, deviceId) => {
|
mockCrypto!.getDeviceVerificationStatus.mockImplementation(async (_userId, deviceId) => {
|
||||||
switch (deviceId) {
|
switch (deviceId) {
|
||||||
case currentDevice.deviceId:
|
case currentDevice.deviceId:
|
||||||
case device2.deviceId:
|
case device2.deviceId:
|
||||||
|
@ -438,7 +444,7 @@ describe("DeviceListener", () => {
|
||||||
|
|
||||||
it("hides toast when current device is unverified", async () => {
|
it("hides toast when current device is unverified", async () => {
|
||||||
// device2 verified, current and device3 unverified
|
// device2 verified, current and device3 unverified
|
||||||
mockClient!.checkDeviceTrust.mockImplementation((_userId, deviceId) => {
|
mockCrypto!.getDeviceVerificationStatus.mockImplementation(async (_userId, deviceId) => {
|
||||||
switch (deviceId) {
|
switch (deviceId) {
|
||||||
case device2.deviceId:
|
case device2.deviceId:
|
||||||
return deviceTrustVerified;
|
return deviceTrustVerified;
|
||||||
|
@ -454,7 +460,7 @@ describe("DeviceListener", () => {
|
||||||
it("hides toast when reminder is snoozed", async () => {
|
it("hides toast when reminder is snoozed", async () => {
|
||||||
mocked(isBulkUnverifiedDeviceReminderSnoozed).mockReturnValue(true);
|
mocked(isBulkUnverifiedDeviceReminderSnoozed).mockReturnValue(true);
|
||||||
// currentDevice, device2 are verified, device3 is unverified
|
// currentDevice, device2 are verified, device3 is unverified
|
||||||
mockClient!.checkDeviceTrust.mockImplementation((_userId, deviceId) => {
|
mockCrypto!.getDeviceVerificationStatus.mockImplementation(async (_userId, deviceId) => {
|
||||||
switch (deviceId) {
|
switch (deviceId) {
|
||||||
case currentDevice.deviceId:
|
case currentDevice.deviceId:
|
||||||
case device2.deviceId:
|
case device2.deviceId:
|
||||||
|
@ -470,7 +476,7 @@ describe("DeviceListener", () => {
|
||||||
|
|
||||||
it("shows toast with unverified devices at app start", async () => {
|
it("shows toast with unverified devices at app start", async () => {
|
||||||
// currentDevice, device2 are verified, device3 is unverified
|
// currentDevice, device2 are verified, device3 is unverified
|
||||||
mockClient!.checkDeviceTrust.mockImplementation((_userId, deviceId) => {
|
mockCrypto!.getDeviceVerificationStatus.mockImplementation(async (_userId, deviceId) => {
|
||||||
switch (deviceId) {
|
switch (deviceId) {
|
||||||
case currentDevice.deviceId:
|
case currentDevice.deviceId:
|
||||||
case device2.deviceId:
|
case device2.deviceId:
|
||||||
|
@ -488,7 +494,7 @@ describe("DeviceListener", () => {
|
||||||
|
|
||||||
it("hides toast when unverified sessions at app start have been dismissed", async () => {
|
it("hides toast when unverified sessions at app start have been dismissed", async () => {
|
||||||
// currentDevice, device2 are verified, device3 is unverified
|
// currentDevice, device2 are verified, device3 is unverified
|
||||||
mockClient!.checkDeviceTrust.mockImplementation((_userId, deviceId) => {
|
mockCrypto!.getDeviceVerificationStatus.mockImplementation(async (_userId, deviceId) => {
|
||||||
switch (deviceId) {
|
switch (deviceId) {
|
||||||
case currentDevice.deviceId:
|
case currentDevice.deviceId:
|
||||||
case device2.deviceId:
|
case device2.deviceId:
|
||||||
|
@ -510,7 +516,7 @@ describe("DeviceListener", () => {
|
||||||
|
|
||||||
it("hides toast when unverified sessions are added after app start", async () => {
|
it("hides toast when unverified sessions are added after app start", async () => {
|
||||||
// currentDevice, device2 are verified, device3 is unverified
|
// currentDevice, device2 are verified, device3 is unverified
|
||||||
mockClient!.checkDeviceTrust.mockImplementation((_userId, deviceId) => {
|
mockCrypto!.getDeviceVerificationStatus.mockImplementation(async (_userId, deviceId) => {
|
||||||
switch (deviceId) {
|
switch (deviceId) {
|
||||||
case currentDevice.deviceId:
|
case currentDevice.deviceId:
|
||||||
case device2.deviceId:
|
case device2.deviceId:
|
||||||
|
|
|
@ -18,9 +18,18 @@ import React from "react";
|
||||||
import { fireEvent, render, screen, waitFor, cleanup, act, within } from "@testing-library/react";
|
import { fireEvent, render, screen, waitFor, cleanup, act, within } from "@testing-library/react";
|
||||||
import userEvent from "@testing-library/user-event";
|
import userEvent from "@testing-library/user-event";
|
||||||
import { Mocked, mocked } from "jest-mock";
|
import { Mocked, mocked } from "jest-mock";
|
||||||
import { Room, User, MatrixClient, RoomMember, MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
|
import {
|
||||||
|
Room,
|
||||||
|
User,
|
||||||
|
MatrixClient,
|
||||||
|
RoomMember,
|
||||||
|
MatrixEvent,
|
||||||
|
EventType,
|
||||||
|
CryptoApi,
|
||||||
|
DeviceVerificationStatus,
|
||||||
|
} from "matrix-js-sdk/src/matrix";
|
||||||
import { Phase, VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
|
import { Phase, VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
|
||||||
import { DeviceTrustLevel, UserTrustLevel } from "matrix-js-sdk/src/crypto/CrossSigning";
|
import { UserTrustLevel } from "matrix-js-sdk/src/crypto/CrossSigning";
|
||||||
import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo";
|
import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo";
|
||||||
import { defer } from "matrix-js-sdk/src/utils";
|
import { defer } from "matrix-js-sdk/src/utils";
|
||||||
|
|
||||||
|
@ -79,6 +88,7 @@ const defaultUser = new User(defaultUserId);
|
||||||
let mockRoom: Mocked<Room>;
|
let mockRoom: Mocked<Room>;
|
||||||
let mockSpace: Mocked<Room>;
|
let mockSpace: Mocked<Room>;
|
||||||
let mockClient: Mocked<MatrixClient>;
|
let mockClient: Mocked<MatrixClient>;
|
||||||
|
let mockCrypto: Mocked<CryptoApi>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockRoom = mocked({
|
mockRoom = mocked({
|
||||||
|
@ -115,6 +125,10 @@ beforeEach(() => {
|
||||||
getEventReadUpTo: jest.fn(),
|
getEventReadUpTo: jest.fn(),
|
||||||
} as unknown as Room);
|
} as unknown as Room);
|
||||||
|
|
||||||
|
mockCrypto = mocked({
|
||||||
|
getDeviceVerificationStatus: jest.fn(),
|
||||||
|
} as unknown as CryptoApi);
|
||||||
|
|
||||||
mockClient = mocked({
|
mockClient = mocked({
|
||||||
getUser: jest.fn(),
|
getUser: jest.fn(),
|
||||||
isGuest: jest.fn().mockReturnValue(false),
|
isGuest: jest.fn().mockReturnValue(false),
|
||||||
|
@ -134,13 +148,13 @@ beforeEach(() => {
|
||||||
currentState: {
|
currentState: {
|
||||||
on: jest.fn(),
|
on: jest.fn(),
|
||||||
},
|
},
|
||||||
checkDeviceTrust: jest.fn(),
|
|
||||||
checkUserTrust: jest.fn(),
|
checkUserTrust: jest.fn(),
|
||||||
getRoom: jest.fn(),
|
getRoom: jest.fn(),
|
||||||
credentials: {},
|
credentials: {},
|
||||||
setPowerLevel: jest.fn(),
|
setPowerLevel: jest.fn(),
|
||||||
downloadKeys: jest.fn(),
|
downloadKeys: jest.fn(),
|
||||||
getStoredDevicesForUser: jest.fn(),
|
getStoredDevicesForUser: jest.fn(),
|
||||||
|
getCrypto: jest.fn().mockReturnValue(mockCrypto),
|
||||||
} as unknown as MatrixClient);
|
} as unknown as MatrixClient);
|
||||||
|
|
||||||
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient);
|
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient);
|
||||||
|
@ -251,7 +265,6 @@ describe("<UserInfo />", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockClient.isCryptoEnabled.mockReturnValue(true);
|
mockClient.isCryptoEnabled.mockReturnValue(true);
|
||||||
mockClient.checkUserTrust.mockReturnValue(new UserTrustLevel(false, false, false));
|
mockClient.checkUserTrust.mockReturnValue(new UserTrustLevel(false, false, false));
|
||||||
mockClient.checkDeviceTrust.mockReturnValue(new DeviceTrustLevel(false, false, false, false));
|
|
||||||
|
|
||||||
const device1 = DeviceInfo.fromStorage(
|
const device1 = DeviceInfo.fromStorage(
|
||||||
{
|
{
|
||||||
|
@ -370,10 +383,10 @@ describe("<DeviceItem />", () => {
|
||||||
mockClient.checkUserTrust.mockReturnValue({ isVerified: () => isVerified } as UserTrustLevel);
|
mockClient.checkUserTrust.mockReturnValue({ isVerified: () => isVerified } as UserTrustLevel);
|
||||||
};
|
};
|
||||||
const setMockDeviceTrust = (isVerified = false, isCrossSigningVerified = false) => {
|
const setMockDeviceTrust = (isVerified = false, isCrossSigningVerified = false) => {
|
||||||
mockClient.checkDeviceTrust.mockReturnValue({
|
mockCrypto.getDeviceVerificationStatus.mockResolvedValue({
|
||||||
isVerified: () => isVerified,
|
isVerified: () => isVerified,
|
||||||
isCrossSigningVerified: () => isCrossSigningVerified,
|
crossSigningVerified: isCrossSigningVerified,
|
||||||
} as DeviceTrustLevel);
|
} as DeviceVerificationStatus);
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockVerifyDevice = jest.spyOn(mockVerification, "verifyDevice");
|
const mockVerifyDevice = jest.spyOn(mockVerification, "verifyDevice");
|
||||||
|
@ -384,7 +397,7 @@ describe("<DeviceItem />", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
mockClient.checkDeviceTrust.mockReset();
|
mockCrypto.getDeviceVerificationStatus.mockReset();
|
||||||
mockClient.checkUserTrust.mockReset();
|
mockClient.checkUserTrust.mockReset();
|
||||||
mockVerifyDevice.mockClear();
|
mockVerifyDevice.mockClear();
|
||||||
});
|
});
|
||||||
|
@ -393,32 +406,36 @@ describe("<DeviceItem />", () => {
|
||||||
mockVerifyDevice.mockRestore();
|
mockVerifyDevice.mockRestore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("with unverified user and device, displays button without a label", () => {
|
it("with unverified user and device, displays button without a label", async () => {
|
||||||
renderComponent();
|
renderComponent();
|
||||||
|
await act(flushPromises);
|
||||||
|
|
||||||
expect(screen.getByRole("button", { name: device.getDisplayName()! })).toBeInTheDocument;
|
expect(screen.getByRole("button", { name: device.getDisplayName()! })).toBeInTheDocument;
|
||||||
expect(screen.queryByText(/trusted/i)).not.toBeInTheDocument();
|
expect(screen.queryByText(/trusted/i)).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("with verified user only, displays button with a 'Not trusted' label", () => {
|
it("with verified user only, displays button with a 'Not trusted' label", async () => {
|
||||||
setMockUserTrust(true);
|
setMockUserTrust(true);
|
||||||
renderComponent();
|
renderComponent();
|
||||||
|
await act(flushPromises);
|
||||||
|
|
||||||
expect(screen.getByRole("button", { name: `${device.getDisplayName()} Not trusted` })).toBeInTheDocument;
|
expect(screen.getByRole("button", { name: `${device.getDisplayName()} Not trusted` })).toBeInTheDocument;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("with verified device only, displays no button without a label", () => {
|
it("with verified device only, displays no button without a label", async () => {
|
||||||
setMockDeviceTrust(true);
|
setMockDeviceTrust(true);
|
||||||
renderComponent();
|
renderComponent();
|
||||||
|
await act(flushPromises);
|
||||||
|
|
||||||
expect(screen.getByText(device.getDisplayName()!)).toBeInTheDocument();
|
expect(screen.getByText(device.getDisplayName()!)).toBeInTheDocument();
|
||||||
expect(screen.queryByText(/trusted/)).not.toBeInTheDocument();
|
expect(screen.queryByText(/trusted/)).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("when userId is the same as userId from client, uses isCrossSigningVerified to determine if button is shown", () => {
|
it("when userId is the same as userId from client, uses isCrossSigningVerified to determine if button is shown", async () => {
|
||||||
mockClient.getSafeUserId.mockReturnValueOnce(defaultUserId);
|
mockClient.getSafeUserId.mockReturnValueOnce(defaultUserId);
|
||||||
mockClient.getUserId.mockReturnValueOnce(defaultUserId);
|
mockClient.getUserId.mockReturnValueOnce(defaultUserId);
|
||||||
renderComponent();
|
renderComponent();
|
||||||
|
await act(flushPromises);
|
||||||
|
|
||||||
// set trust to be false for isVerified, true for isCrossSigningVerified
|
// set trust to be false for isVerified, true for isCrossSigningVerified
|
||||||
setMockDeviceTrust(false, true);
|
setMockDeviceTrust(false, true);
|
||||||
|
@ -428,10 +445,11 @@ describe("<DeviceItem />", () => {
|
||||||
expect(screen.getByText(device.getDisplayName()!)).toBeInTheDocument();
|
expect(screen.getByText(device.getDisplayName()!)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("with verified user and device, displays no button and a 'Trusted' label", () => {
|
it("with verified user and device, displays no button and a 'Trusted' label", async () => {
|
||||||
setMockUserTrust(true);
|
setMockUserTrust(true);
|
||||||
setMockDeviceTrust(true);
|
setMockDeviceTrust(true);
|
||||||
renderComponent();
|
renderComponent();
|
||||||
|
await act(flushPromises);
|
||||||
|
|
||||||
expect(screen.queryByRole("button")).not.toBeInTheDocument;
|
expect(screen.queryByRole("button")).not.toBeInTheDocument;
|
||||||
expect(screen.getByText(device.getDisplayName()!)).toBeInTheDocument();
|
expect(screen.getByText(device.getDisplayName()!)).toBeInTheDocument();
|
||||||
|
@ -441,6 +459,7 @@ describe("<DeviceItem />", () => {
|
||||||
it("does not call verifyDevice if client.getUser returns null", async () => {
|
it("does not call verifyDevice if client.getUser returns null", async () => {
|
||||||
mockClient.getUser.mockReturnValueOnce(null);
|
mockClient.getUser.mockReturnValueOnce(null);
|
||||||
renderComponent();
|
renderComponent();
|
||||||
|
await act(flushPromises);
|
||||||
|
|
||||||
const button = screen.getByRole("button", { name: device.getDisplayName()! });
|
const button = screen.getByRole("button", { name: device.getDisplayName()! });
|
||||||
expect(button).toBeInTheDocument;
|
expect(button).toBeInTheDocument;
|
||||||
|
@ -455,6 +474,7 @@ describe("<DeviceItem />", () => {
|
||||||
// even more mocking
|
// even more mocking
|
||||||
mockClient.isGuest.mockReturnValueOnce(true);
|
mockClient.isGuest.mockReturnValueOnce(true);
|
||||||
renderComponent();
|
renderComponent();
|
||||||
|
await act(flushPromises);
|
||||||
|
|
||||||
const button = screen.getByRole("button", { name: device.getDisplayName()! });
|
const button = screen.getByRole("button", { name: device.getDisplayName()! });
|
||||||
expect(button).toBeInTheDocument;
|
expect(button).toBeInTheDocument;
|
||||||
|
|
|
@ -19,7 +19,7 @@ import { render, waitFor, screen, act, fireEvent } from "@testing-library/react"
|
||||||
import { mocked } from "jest-mock";
|
import { mocked } from "jest-mock";
|
||||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||||
import { MatrixClient, PendingEventOrdering } from "matrix-js-sdk/src/client";
|
import { MatrixClient, PendingEventOrdering } from "matrix-js-sdk/src/client";
|
||||||
import { TweakName } from "matrix-js-sdk/src/matrix";
|
import { CryptoApi, TweakName } from "matrix-js-sdk/src/matrix";
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
import { NotificationCountType, Room } from "matrix-js-sdk/src/models/room";
|
import { NotificationCountType, Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { DeviceTrustLevel, UserTrustLevel } from "matrix-js-sdk/src/crypto/CrossSigning";
|
import { DeviceTrustLevel, UserTrustLevel } from "matrix-js-sdk/src/crypto/CrossSigning";
|
||||||
|
@ -30,7 +30,7 @@ import EventTile, { EventTileProps } from "../../../../src/components/views/room
|
||||||
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
|
||||||
import RoomContext, { TimelineRenderingType } from "../../../../src/contexts/RoomContext";
|
import RoomContext, { TimelineRenderingType } from "../../../../src/contexts/RoomContext";
|
||||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||||
import { getRoomContext, mkEncryptedEvent, mkEvent, mkMessage, stubClient } from "../../../test-utils";
|
import { flushPromises, getRoomContext, mkEncryptedEvent, mkEvent, mkMessage, stubClient } from "../../../test-utils";
|
||||||
import { mkThread } from "../../../test-utils/threads";
|
import { mkThread } from "../../../test-utils/threads";
|
||||||
import DMRoomMap from "../../../../src/utils/DMRoomMap";
|
import DMRoomMap from "../../../../src/utils/DMRoomMap";
|
||||||
import dis from "../../../../src/dispatcher/dispatcher";
|
import dis from "../../../../src/dispatcher/dispatcher";
|
||||||
|
@ -221,13 +221,16 @@ describe("EventTile", () => {
|
||||||
// a version of checkDeviceTrust which says that TRUSTED_DEVICE is trusted, and others are not.
|
// a version of checkDeviceTrust which says that TRUSTED_DEVICE is trusted, and others are not.
|
||||||
const trustedDeviceTrustLevel = DeviceTrustLevel.fromUserTrustLevel(trustedUserTrustLevel, true, false);
|
const trustedDeviceTrustLevel = DeviceTrustLevel.fromUserTrustLevel(trustedUserTrustLevel, true, false);
|
||||||
const untrustedDeviceTrustLevel = DeviceTrustLevel.fromUserTrustLevel(trustedUserTrustLevel, false, false);
|
const untrustedDeviceTrustLevel = DeviceTrustLevel.fromUserTrustLevel(trustedUserTrustLevel, false, false);
|
||||||
client.checkDeviceTrust = (userId, deviceId) => {
|
const mockCrypto = {
|
||||||
if (deviceId === TRUSTED_DEVICE.deviceId) {
|
getDeviceVerificationStatus: async (userId: string, deviceId: string) => {
|
||||||
return trustedDeviceTrustLevel;
|
if (deviceId === TRUSTED_DEVICE.deviceId) {
|
||||||
} else {
|
return trustedDeviceTrustLevel;
|
||||||
return untrustedDeviceTrustLevel;
|
} else {
|
||||||
}
|
return untrustedDeviceTrustLevel;
|
||||||
};
|
}
|
||||||
|
},
|
||||||
|
} as unknown as CryptoApi;
|
||||||
|
client.getCrypto = () => mockCrypto;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows a warning for an event from an unverified device", async () => {
|
it("shows a warning for an event from an unverified device", async () => {
|
||||||
|
@ -243,6 +246,7 @@ describe("EventTile", () => {
|
||||||
} as IEncryptedEventInfo);
|
} as IEncryptedEventInfo);
|
||||||
|
|
||||||
const { container } = getComponent();
|
const { container } = getComponent();
|
||||||
|
await act(flushPromises);
|
||||||
|
|
||||||
const eventTiles = container.getElementsByClassName("mx_EventTile");
|
const eventTiles = container.getElementsByClassName("mx_EventTile");
|
||||||
expect(eventTiles).toHaveLength(1);
|
expect(eventTiles).toHaveLength(1);
|
||||||
|
@ -270,6 +274,7 @@ describe("EventTile", () => {
|
||||||
} as IEncryptedEventInfo);
|
} as IEncryptedEventInfo);
|
||||||
|
|
||||||
const { container } = getComponent();
|
const { container } = getComponent();
|
||||||
|
await act(flushPromises);
|
||||||
|
|
||||||
const eventTiles = container.getElementsByClassName("mx_EventTile");
|
const eventTiles = container.getElementsByClassName("mx_EventTile");
|
||||||
expect(eventTiles).toHaveLength(1);
|
expect(eventTiles).toHaveLength(1);
|
||||||
|
@ -295,6 +300,7 @@ describe("EventTile", () => {
|
||||||
} as IEncryptedEventInfo);
|
} as IEncryptedEventInfo);
|
||||||
|
|
||||||
const { container } = getComponent();
|
const { container } = getComponent();
|
||||||
|
await act(flushPromises);
|
||||||
|
|
||||||
const eventTiles = container.getElementsByClassName("mx_EventTile");
|
const eventTiles = container.getElementsByClassName("mx_EventTile");
|
||||||
expect(eventTiles).toHaveLength(1);
|
expect(eventTiles).toHaveLength(1);
|
||||||
|
@ -317,8 +323,9 @@ describe("EventTile", () => {
|
||||||
sender: UNTRUSTED_DEVICE,
|
sender: UNTRUSTED_DEVICE,
|
||||||
} as IEncryptedEventInfo);
|
} as IEncryptedEventInfo);
|
||||||
|
|
||||||
act(() => {
|
await act(async () => {
|
||||||
mxEvent.makeReplaced(replacementEvent);
|
mxEvent.makeReplaced(replacementEvent);
|
||||||
|
flushPromises();
|
||||||
});
|
});
|
||||||
|
|
||||||
// check it was updated
|
// check it was updated
|
||||||
|
@ -345,6 +352,7 @@ describe("EventTile", () => {
|
||||||
} as IEncryptedEventInfo);
|
} as IEncryptedEventInfo);
|
||||||
|
|
||||||
const { container } = getComponent();
|
const { container } = getComponent();
|
||||||
|
await act(flushPromises);
|
||||||
|
|
||||||
const eventTiles = container.getElementsByClassName("mx_EventTile");
|
const eventTiles = container.getElementsByClassName("mx_EventTile");
|
||||||
expect(eventTiles).toHaveLength(1);
|
expect(eventTiles).toHaveLength(1);
|
||||||
|
@ -363,8 +371,9 @@ describe("EventTile", () => {
|
||||||
event: true,
|
event: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
act(() => {
|
await act(async () => {
|
||||||
mxEvent.makeReplaced(replacementEvent);
|
mxEvent.makeReplaced(replacementEvent);
|
||||||
|
await flushPromises();
|
||||||
});
|
});
|
||||||
|
|
||||||
// check it was updated
|
// check it was updated
|
||||||
|
|
|
@ -18,7 +18,6 @@ import { act, fireEvent, render } from "@testing-library/react";
|
||||||
import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo";
|
import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo";
|
||||||
import { sleep } from "matrix-js-sdk/src/utils";
|
import { sleep } from "matrix-js-sdk/src/utils";
|
||||||
import { PUSHER_DEVICE_ID, PUSHER_ENABLED } from "matrix-js-sdk/src/@types/event";
|
import { PUSHER_DEVICE_ID, PUSHER_ENABLED } from "matrix-js-sdk/src/@types/event";
|
||||||
import { DeviceTrustLevel } from "matrix-js-sdk/src/crypto/CrossSigning";
|
|
||||||
|
|
||||||
import DevicesPanel from "../../../../src/components/views/settings/DevicesPanel";
|
import DevicesPanel from "../../../../src/components/views/settings/DevicesPanel";
|
||||||
import { flushPromises, getMockClientWithEventEmitter, mkPusher, mockClientMethodsUser } from "../../../test-utils";
|
import { flushPromises, getMockClientWithEventEmitter, mkPusher, mockClientMethodsUser } from "../../../test-utils";
|
||||||
|
@ -29,16 +28,21 @@ describe("<DevicesPanel />", () => {
|
||||||
const device1 = { device_id: "device_1" };
|
const device1 = { device_id: "device_1" };
|
||||||
const device2 = { device_id: "device_2" };
|
const device2 = { device_id: "device_2" };
|
||||||
const device3 = { device_id: "device_3" };
|
const device3 = { device_id: "device_3" };
|
||||||
|
const mockCrypto = {
|
||||||
|
getDeviceVerificationStatus: jest.fn().mockResolvedValue({
|
||||||
|
crossSigningVerified: false,
|
||||||
|
}),
|
||||||
|
};
|
||||||
const mockClient = getMockClientWithEventEmitter({
|
const mockClient = getMockClientWithEventEmitter({
|
||||||
...mockClientMethodsUser(userId),
|
...mockClientMethodsUser(userId),
|
||||||
getDevices: jest.fn(),
|
getDevices: jest.fn(),
|
||||||
getDeviceId: jest.fn().mockReturnValue(device1.device_id),
|
getDeviceId: jest.fn().mockReturnValue(device1.device_id),
|
||||||
deleteMultipleDevices: jest.fn(),
|
deleteMultipleDevices: jest.fn(),
|
||||||
checkDeviceTrust: jest.fn().mockReturnValue(new DeviceTrustLevel(false, false, false, false)),
|
|
||||||
getStoredDevice: jest.fn().mockReturnValue(new DeviceInfo("id")),
|
getStoredDevice: jest.fn().mockReturnValue(new DeviceInfo("id")),
|
||||||
generateClientSecret: jest.fn(),
|
generateClientSecret: jest.fn(),
|
||||||
getPushers: jest.fn(),
|
getPushers: jest.fn(),
|
||||||
setPusher: jest.fn(),
|
setPusher: jest.fn(),
|
||||||
|
getCrypto: jest.fn().mockReturnValue(mockCrypto),
|
||||||
});
|
});
|
||||||
|
|
||||||
const getComponent = () => (
|
const getComponent = () => (
|
||||||
|
|
|
@ -18,7 +18,6 @@ import React from "react";
|
||||||
import { act, fireEvent, render, RenderResult } from "@testing-library/react";
|
import { act, fireEvent, render, RenderResult } from "@testing-library/react";
|
||||||
import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo";
|
import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { DeviceTrustLevel } from "matrix-js-sdk/src/crypto/CrossSigning";
|
|
||||||
import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
|
import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
|
||||||
import { defer, sleep } from "matrix-js-sdk/src/utils";
|
import { defer, sleep } from "matrix-js-sdk/src/utils";
|
||||||
import {
|
import {
|
||||||
|
@ -30,7 +29,10 @@ import {
|
||||||
PUSHER_ENABLED,
|
PUSHER_ENABLED,
|
||||||
IAuthData,
|
IAuthData,
|
||||||
UNSTABLE_MSC3882_CAPABILITY,
|
UNSTABLE_MSC3882_CAPABILITY,
|
||||||
|
CryptoApi,
|
||||||
|
DeviceVerificationStatus,
|
||||||
} from "matrix-js-sdk/src/matrix";
|
} from "matrix-js-sdk/src/matrix";
|
||||||
|
import { mocked } from "jest-mock";
|
||||||
|
|
||||||
import { clearAllModals } from "../../../../../test-utils";
|
import { clearAllModals } from "../../../../../test-utils";
|
||||||
import SessionManagerTab from "../../../../../../src/components/views/settings/tabs/user/SessionManagerTab";
|
import SessionManagerTab from "../../../../../../src/components/views/settings/tabs/user/SessionManagerTab";
|
||||||
|
@ -78,9 +80,14 @@ describe("<SessionManagerTab />", () => {
|
||||||
cancel: jest.fn(),
|
cancel: jest.fn(),
|
||||||
on: jest.fn(),
|
on: jest.fn(),
|
||||||
} as unknown as VerificationRequest;
|
} as unknown as VerificationRequest;
|
||||||
|
|
||||||
|
const mockCrypto = mocked({
|
||||||
|
getDeviceVerificationStatus: jest.fn(),
|
||||||
|
} as unknown as CryptoApi);
|
||||||
|
|
||||||
const mockClient = getMockClientWithEventEmitter({
|
const mockClient = getMockClientWithEventEmitter({
|
||||||
...mockClientMethodsUser(aliceId),
|
...mockClientMethodsUser(aliceId),
|
||||||
checkDeviceTrust: jest.fn(),
|
getCrypto: jest.fn().mockReturnValue(mockCrypto),
|
||||||
getDevices: jest.fn(),
|
getDevices: jest.fn(),
|
||||||
getStoredDevice: jest.fn(),
|
getStoredDevice: jest.fn(),
|
||||||
getDeviceId: jest.fn().mockReturnValue(deviceId),
|
getDeviceId: jest.fn().mockReturnValue(deviceId),
|
||||||
|
@ -171,7 +178,7 @@ describe("<SessionManagerTab />", () => {
|
||||||
const device = [alicesDevice, alicesMobileDevice].find((device) => device.device_id === id);
|
const device = [alicesDevice, alicesMobileDevice].find((device) => device.device_id === id);
|
||||||
return device ? new DeviceInfo(device.device_id) : null;
|
return device ? new DeviceInfo(device.device_id) : null;
|
||||||
});
|
});
|
||||||
mockClient.checkDeviceTrust.mockReset().mockReturnValue(new DeviceTrustLevel(false, false, false, false));
|
mockCrypto.getDeviceVerificationStatus.mockReset().mockResolvedValue(new DeviceVerificationStatus({}));
|
||||||
|
|
||||||
mockClient.getDevices.mockReset().mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] });
|
mockClient.getDevices.mockReset().mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] });
|
||||||
|
|
||||||
|
@ -221,13 +228,13 @@ describe("<SessionManagerTab />", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not fail when checking device verification fails", async () => {
|
it("does not fail when checking device verification fails", async () => {
|
||||||
const logSpy = jest.spyOn(console, "error").mockImplementation(() => {});
|
const logSpy = jest.spyOn(console, "error").mockImplementation((e) => {});
|
||||||
mockClient.getDevices.mockResolvedValue({
|
mockClient.getDevices.mockResolvedValue({
|
||||||
devices: [alicesDevice, alicesMobileDevice],
|
devices: [alicesDevice, alicesMobileDevice],
|
||||||
});
|
});
|
||||||
const noCryptoError = new Error("End-to-end encryption disabled");
|
const failError = new Error("non-specific failure");
|
||||||
mockClient.checkDeviceTrust.mockImplementation(() => {
|
mockCrypto.getDeviceVerificationStatus.mockImplementation(() => {
|
||||||
throw noCryptoError;
|
throw failError;
|
||||||
});
|
});
|
||||||
render(getComponent());
|
render(getComponent());
|
||||||
|
|
||||||
|
@ -236,9 +243,9 @@ describe("<SessionManagerTab />", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// called for each device despite error
|
// called for each device despite error
|
||||||
expect(mockClient.checkDeviceTrust).toHaveBeenCalledWith(aliceId, alicesDevice.device_id);
|
expect(mockCrypto.getDeviceVerificationStatus).toHaveBeenCalledWith(aliceId, alicesDevice.device_id);
|
||||||
expect(mockClient.checkDeviceTrust).toHaveBeenCalledWith(aliceId, alicesMobileDevice.device_id);
|
expect(mockCrypto.getDeviceVerificationStatus).toHaveBeenCalledWith(aliceId, alicesMobileDevice.device_id);
|
||||||
expect(logSpy).toHaveBeenCalledWith("Error getting device cross-signing info", noCryptoError);
|
expect(logSpy).toHaveBeenCalledWith("Error getting device cross-signing info", failError);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sets device verification status correctly", async () => {
|
it("sets device verification status correctly", async () => {
|
||||||
|
@ -246,14 +253,14 @@ describe("<SessionManagerTab />", () => {
|
||||||
devices: [alicesDevice, alicesMobileDevice, alicesOlderMobileDevice],
|
devices: [alicesDevice, alicesMobileDevice, alicesOlderMobileDevice],
|
||||||
});
|
});
|
||||||
mockClient.getStoredDevice.mockImplementation((_userId, deviceId) => new DeviceInfo(deviceId));
|
mockClient.getStoredDevice.mockImplementation((_userId, deviceId) => new DeviceInfo(deviceId));
|
||||||
mockClient.checkDeviceTrust.mockImplementation((_userId, deviceId) => {
|
mockCrypto.getDeviceVerificationStatus.mockImplementation(async (_userId, deviceId) => {
|
||||||
// alices device is trusted
|
// alices device is trusted
|
||||||
if (deviceId === alicesDevice.device_id) {
|
if (deviceId === alicesDevice.device_id) {
|
||||||
return new DeviceTrustLevel(true, true, false, false);
|
return new DeviceVerificationStatus({ crossSigningVerified: true, localVerified: true });
|
||||||
}
|
}
|
||||||
// alices mobile device is not
|
// alices mobile device is not
|
||||||
if (deviceId === alicesMobileDevice.device_id) {
|
if (deviceId === alicesMobileDevice.device_id) {
|
||||||
return new DeviceTrustLevel(false, false, false, false);
|
return new DeviceVerificationStatus({});
|
||||||
}
|
}
|
||||||
// alicesOlderMobileDevice does not support encryption
|
// alicesOlderMobileDevice does not support encryption
|
||||||
throw new Error("encryption not supported");
|
throw new Error("encryption not supported");
|
||||||
|
@ -265,7 +272,7 @@ describe("<SessionManagerTab />", () => {
|
||||||
await flushPromises();
|
await flushPromises();
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mockClient.checkDeviceTrust).toHaveBeenCalledTimes(3);
|
expect(mockCrypto.getDeviceVerificationStatus).toHaveBeenCalledTimes(3);
|
||||||
expect(
|
expect(
|
||||||
getByTestId(`device-tile-${alicesDevice.device_id}`).querySelector('[aria-label="Verified"]'),
|
getByTestId(`device-tile-${alicesDevice.device_id}`).querySelector('[aria-label="Verified"]'),
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
|
@ -418,7 +425,9 @@ describe("<SessionManagerTab />", () => {
|
||||||
devices: [alicesDevice, alicesMobileDevice],
|
devices: [alicesDevice, alicesMobileDevice],
|
||||||
});
|
});
|
||||||
mockClient.getStoredDevice.mockImplementation(() => new DeviceInfo(alicesDevice.device_id));
|
mockClient.getStoredDevice.mockImplementation(() => new DeviceInfo(alicesDevice.device_id));
|
||||||
mockClient.checkDeviceTrust.mockReturnValue(new DeviceTrustLevel(true, true, false, false));
|
mockCrypto.getDeviceVerificationStatus.mockResolvedValue(
|
||||||
|
new DeviceVerificationStatus({ crossSigningVerified: true, localVerified: true }),
|
||||||
|
);
|
||||||
|
|
||||||
const { getByTestId } = render(getComponent());
|
const { getByTestId } = render(getComponent());
|
||||||
|
|
||||||
|
@ -520,11 +529,11 @@ describe("<SessionManagerTab />", () => {
|
||||||
devices: [alicesDevice, alicesMobileDevice],
|
devices: [alicesDevice, alicesMobileDevice],
|
||||||
});
|
});
|
||||||
mockClient.getStoredDevice.mockImplementation((_userId, deviceId) => new DeviceInfo(deviceId));
|
mockClient.getStoredDevice.mockImplementation((_userId, deviceId) => new DeviceInfo(deviceId));
|
||||||
mockClient.checkDeviceTrust.mockImplementation((_userId, deviceId) => {
|
mockCrypto.getDeviceVerificationStatus.mockImplementation(async (_userId, deviceId) => {
|
||||||
if (deviceId === alicesDevice.device_id) {
|
if (deviceId === alicesDevice.device_id) {
|
||||||
return new DeviceTrustLevel(true, true, false, false);
|
return new DeviceVerificationStatus({ crossSigningVerified: true, localVerified: true });
|
||||||
}
|
}
|
||||||
return new DeviceTrustLevel(false, false, false, false);
|
return new DeviceVerificationStatus({});
|
||||||
});
|
});
|
||||||
|
|
||||||
const { getByTestId } = render(getComponent());
|
const { getByTestId } = render(getComponent());
|
||||||
|
@ -547,12 +556,13 @@ describe("<SessionManagerTab />", () => {
|
||||||
devices: [alicesDevice, alicesMobileDevice],
|
devices: [alicesDevice, alicesMobileDevice],
|
||||||
});
|
});
|
||||||
mockClient.getStoredDevice.mockImplementation((_userId, deviceId) => new DeviceInfo(deviceId));
|
mockClient.getStoredDevice.mockImplementation((_userId, deviceId) => new DeviceInfo(deviceId));
|
||||||
mockClient.checkDeviceTrust.mockImplementation((_userId, deviceId) => {
|
mockCrypto.getDeviceVerificationStatus.mockImplementation(async (_userId, deviceId) => {
|
||||||
// current session verified = able to verify other sessions
|
// current session verified = able to verify other sessions
|
||||||
if (deviceId === alicesDevice.device_id) {
|
if (deviceId === alicesDevice.device_id) {
|
||||||
return new DeviceTrustLevel(true, true, false, false);
|
return new DeviceVerificationStatus({ crossSigningVerified: true, localVerified: true });
|
||||||
}
|
}
|
||||||
// but alicesMobileDevice doesn't support encryption
|
// but alicesMobileDevice doesn't support encryption
|
||||||
|
// XXX this is not what happens if a device doesn't support encryption.
|
||||||
throw new Error("encryption not supported");
|
throw new Error("encryption not supported");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -581,11 +591,11 @@ describe("<SessionManagerTab />", () => {
|
||||||
devices: [alicesDevice, alicesMobileDevice],
|
devices: [alicesDevice, alicesMobileDevice],
|
||||||
});
|
});
|
||||||
mockClient.getStoredDevice.mockImplementation((_userId, deviceId) => new DeviceInfo(deviceId));
|
mockClient.getStoredDevice.mockImplementation((_userId, deviceId) => new DeviceInfo(deviceId));
|
||||||
mockClient.checkDeviceTrust.mockImplementation((_userId, deviceId) => {
|
mockCrypto.getDeviceVerificationStatus.mockImplementation(async (_userId, deviceId) => {
|
||||||
if (deviceId === alicesDevice.device_id) {
|
if (deviceId === alicesDevice.device_id) {
|
||||||
return new DeviceTrustLevel(true, true, false, false);
|
return new DeviceVerificationStatus({ crossSigningVerified: true, localVerified: true });
|
||||||
}
|
}
|
||||||
return new DeviceTrustLevel(false, false, false, false);
|
return new DeviceVerificationStatus({});
|
||||||
});
|
});
|
||||||
|
|
||||||
const { getByTestId } = render(getComponent());
|
const { getByTestId } = render(getComponent());
|
||||||
|
|
|
@ -99,7 +99,6 @@ export function createTestClient(): MatrixClient {
|
||||||
getDevice: jest.fn(),
|
getDevice: jest.fn(),
|
||||||
getDeviceId: jest.fn().mockReturnValue("ABCDEFGHI"),
|
getDeviceId: jest.fn().mockReturnValue("ABCDEFGHI"),
|
||||||
getStoredCrossSigningForUser: jest.fn(),
|
getStoredCrossSigningForUser: jest.fn(),
|
||||||
checkDeviceTrust: jest.fn(),
|
|
||||||
getStoredDevice: jest.fn(),
|
getStoredDevice: jest.fn(),
|
||||||
requestVerification: jest.fn(),
|
requestVerification: jest.fn(),
|
||||||
deviceId: "ABCDEFGHI",
|
deviceId: "ABCDEFGHI",
|
||||||
|
@ -234,6 +233,7 @@ export function createTestClient(): MatrixClient {
|
||||||
}),
|
}),
|
||||||
|
|
||||||
searchUserDirectory: jest.fn().mockResolvedValue({ limited: false, results: [] }),
|
searchUserDirectory: jest.fn().mockResolvedValue({ limited: false, results: [] }),
|
||||||
|
getCrypto: jest.fn(),
|
||||||
} as unknown as MatrixClient;
|
} as unknown as MatrixClient;
|
||||||
|
|
||||||
client.reEmitter = new ReEmitter(client);
|
client.reEmitter = new ReEmitter(client);
|
||||||
|
|
|
@ -18,9 +18,8 @@ import React from "react";
|
||||||
import { render, RenderResult, screen } from "@testing-library/react";
|
import { render, RenderResult, screen } from "@testing-library/react";
|
||||||
import userEvent from "@testing-library/user-event";
|
import userEvent from "@testing-library/user-event";
|
||||||
import { mocked, Mocked } from "jest-mock";
|
import { mocked, Mocked } from "jest-mock";
|
||||||
import { IMyDevice, MatrixClient } from "matrix-js-sdk/src/matrix";
|
import { CryptoApi, DeviceVerificationStatus, IMyDevice, MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||||
import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo";
|
import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo";
|
||||||
import { DeviceTrustLevel } from "matrix-js-sdk/src/crypto/CrossSigning";
|
|
||||||
|
|
||||||
import dis from "../../src/dispatcher/dispatcher";
|
import dis from "../../src/dispatcher/dispatcher";
|
||||||
import { showToast } from "../../src/toasts/UnverifiedSessionToast";
|
import { showToast } from "../../src/toasts/UnverifiedSessionToast";
|
||||||
|
@ -55,7 +54,11 @@ describe("UnverifiedSessionToast", () => {
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
client.checkDeviceTrust.mockReturnValue(new DeviceTrustLevel(true, false, false, false));
|
client.getCrypto.mockReturnValue({
|
||||||
|
getDeviceVerificationStatus: jest
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue(new DeviceVerificationStatus({ crossSigningVerified: true })),
|
||||||
|
} as unknown as CryptoApi);
|
||||||
jest.spyOn(dis, "dispatch");
|
jest.spyOn(dis, "dispatch");
|
||||||
jest.spyOn(DeviceListener.sharedInstance(), "dismissUnverifiedSessions");
|
jest.spyOn(DeviceListener.sharedInstance(), "dismissUnverifiedSessions");
|
||||||
});
|
});
|
||||||
|
|
|
@ -22,21 +22,25 @@ import DMRoomMap from "../../src/utils/DMRoomMap";
|
||||||
function mkClient(selfTrust = false) {
|
function mkClient(selfTrust = false) {
|
||||||
return {
|
return {
|
||||||
getUserId: () => "@self:localhost",
|
getUserId: () => "@self:localhost",
|
||||||
|
getCrypto: () => ({
|
||||||
|
getDeviceVerificationStatus: (userId: string, deviceId: string) =>
|
||||||
|
Promise.resolve({
|
||||||
|
isVerified: () => (userId === "@self:localhost" ? selfTrust : userId[2] == "T"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
checkUserTrust: (userId: string) => ({
|
checkUserTrust: (userId: string) => ({
|
||||||
isCrossSigningVerified: () => userId[1] == "T",
|
isCrossSigningVerified: () => userId[1] == "T",
|
||||||
wasCrossSigningVerified: () => userId[1] == "T" || userId[1] == "W",
|
wasCrossSigningVerified: () => userId[1] == "T" || userId[1] == "W",
|
||||||
}),
|
}),
|
||||||
checkDeviceTrust: (userId: string, deviceId: string) => ({
|
|
||||||
isVerified: () => (userId === "@self:localhost" ? selfTrust : userId[2] == "T"),
|
|
||||||
}),
|
|
||||||
getStoredDevicesForUser: (userId: string) => ["DEVICE"],
|
getStoredDevicesForUser: (userId: string) => ["DEVICE"],
|
||||||
} as unknown as MatrixClient;
|
} as unknown as MatrixClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("mkClient self-test", function () {
|
describe("mkClient self-test", function () {
|
||||||
test.each([true, false])("behaves well for self-trust=%s", (v) => {
|
test.each([true, false])("behaves well for self-trust=%s", async (v) => {
|
||||||
const client = mkClient(v);
|
const client = mkClient(v);
|
||||||
expect(client.checkDeviceTrust("@self:localhost", "DEVICE").isVerified()).toBe(v);
|
const status = await client.getCrypto()!.getDeviceVerificationStatus("@self:localhost", "DEVICE");
|
||||||
|
expect(status?.isVerified()).toBe(v);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.each([
|
test.each([
|
||||||
|
@ -53,8 +57,9 @@ describe("mkClient self-test", function () {
|
||||||
["@TF:h", false],
|
["@TF:h", false],
|
||||||
["@FT:h", true],
|
["@FT:h", true],
|
||||||
["@FF:h", false],
|
["@FF:h", false],
|
||||||
])("behaves well for device trust %s", (userId, trust) => {
|
])("behaves well for device trust %s", async (userId, trust) => {
|
||||||
expect(mkClient().checkDeviceTrust(userId, "device").isVerified()).toBe(trust);
|
const status = await mkClient().getCrypto()!.getDeviceVerificationStatus(userId, "device");
|
||||||
|
expect(status?.isVerified()).toBe(trust);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ import {
|
||||||
GroupedArray,
|
GroupedArray,
|
||||||
concat,
|
concat,
|
||||||
asyncEvery,
|
asyncEvery,
|
||||||
|
asyncSome,
|
||||||
} from "../../src/utils/arrays";
|
} from "../../src/utils/arrays";
|
||||||
|
|
||||||
type TestParams = { input: number[]; output: number[] };
|
type TestParams = { input: number[]; output: number[] };
|
||||||
|
@ -444,4 +445,27 @@ describe("arrays", () => {
|
||||||
expect(predicate).toHaveBeenCalledWith(2);
|
expect(predicate).toHaveBeenCalledWith(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("asyncSome", () => {
|
||||||
|
it("when called with an empty array, it should return false", async () => {
|
||||||
|
expect(await asyncSome([], jest.fn().mockResolvedValue(true))).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when called with some items and the predicate resolves to false for all of them, it should return false", async () => {
|
||||||
|
const predicate = jest.fn().mockResolvedValue(false);
|
||||||
|
expect(await asyncSome([1, 2, 3], predicate)).toBe(false);
|
||||||
|
expect(predicate).toHaveBeenCalledTimes(3);
|
||||||
|
expect(predicate).toHaveBeenCalledWith(1);
|
||||||
|
expect(predicate).toHaveBeenCalledWith(2);
|
||||||
|
expect(predicate).toHaveBeenCalledWith(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when called with some items and the predicate resolves to true, it should short-circuit and return true", async () => {
|
||||||
|
const predicate = jest.fn().mockResolvedValueOnce(false).mockResolvedValueOnce(true);
|
||||||
|
expect(await asyncSome([1, 2, 3], predicate)).toBe(true);
|
||||||
|
expect(predicate).toHaveBeenCalledTimes(2);
|
||||||
|
expect(predicate).toHaveBeenCalledWith(1);
|
||||||
|
expect(predicate).toHaveBeenCalledWith(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue