render unverified device verification tile

This commit is contained in:
Ajay Bura 2025-01-29 21:42:05 +05:30
parent 18357cee95
commit 55248378f4
8 changed files with 159 additions and 3 deletions

View file

@ -0,0 +1,24 @@
import { ReactNode } from 'react';
import { CryptoApi } from 'matrix-js-sdk/lib/crypto-api';
import {
useDeviceVerificationStatus,
VerificationStatus,
} from '../hooks/useDeviceVerificationStatus';
type DeviceVerificationStatusProps = {
crypto?: CryptoApi;
userId: string;
deviceId: string;
children: (verificationStatus: VerificationStatus) => ReactNode;
};
export function DeviceVerificationStatus({
crypto,
userId,
deviceId,
children,
}: DeviceVerificationStatusProps) {
const status = useDeviceVerificationStatus(crypto, userId, deviceId);
return children(status);
}

View file

@ -170,7 +170,7 @@ export function DeviceLogoutBtn() {
return (
<>
<Chip variant="Critical" fill="Soft" radii="Pill" outlined onClick={() => setPrompt(true)}>
<Chip variant="Secondary" fill="Soft" radii="Pill" onClick={() => setPrompt(true)}>
<Text size="B300">Logout</Text>
</Chip>
{prompt && (

View file

@ -9,6 +9,11 @@ import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { LocalBackup } from './LocalBackup';
import { DeviceLogoutBtn, DeviceTile, DeviceTilePlaceholder } from './DeviceTile';
import { OtherDevices } from './OtherDevices';
import { ManualVerificationTile } from './Verificaton';
import {
useDeviceVerificationStatus,
VerificationStatus,
} from '../../../hooks/useDeviceVerificationStatus';
function DevicesPlaceholder() {
return (
@ -24,6 +29,7 @@ type DevicesProps = {
};
export function Devices({ requestClose }: DevicesProps) {
const mx = useMatrixClient();
const crypto = mx.getCrypto();
const [devices, refreshDeviceList] = useDeviceList();
const currentDeviceId = mx.getDeviceId();
const currentDevice = currentDeviceId
@ -31,6 +37,12 @@ export function Devices({ requestClose }: DevicesProps) {
: undefined;
const otherDevices = devices?.filter((device) => device.device_id !== currentDeviceId);
const verificationStatus = useDeviceVerificationStatus(
crypto,
mx.getSafeUserId(),
currentDevice?.device_id
);
return (
<Page>
<PageHeader outlined={false}>
@ -86,6 +98,9 @@ export function Devices({ requestClose }: DevicesProps) {
refreshDeviceList={refreshDeviceList}
options={<DeviceLogoutBtn />}
/>
{verificationStatus === VerificationStatus.Unverified && (
<ManualVerificationTile />
)}
</SequenceCard>
) : (
<DeviceTilePlaceholder />
@ -93,7 +108,11 @@ export function Devices({ requestClose }: DevicesProps) {
</Box>
{devices === null && <DevicesPlaceholder />}
{otherDevices && (
<OtherDevices devices={otherDevices} refreshDeviceList={refreshDeviceList} />
<OtherDevices
devices={otherDevices}
refreshDeviceList={refreshDeviceList}
verified={verificationStatus === VerificationStatus.Verified}
/>
)}
<LocalBackup />
</Box>

View file

@ -8,13 +8,18 @@ import { DeviceDeleteBtn, DeviceTile } from './DeviceTile';
import { AsyncState, AsyncStatus, useAsync } from '../../../hooks/useAsyncCallback';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { useUIAMatrixError } from '../../../hooks/useUIAFlows';
import { DeviceVerificationStatus } from '../../../components/DeviceVerificationStatus';
import { StartVerificationTile } from './Verificaton';
import { VerificationStatus } from '../../../hooks/useDeviceVerificationStatus';
type OtherDevicesProps = {
devices: IMyDevice[];
refreshDeviceList: () => Promise<void>;
verified?: boolean;
};
export function OtherDevices({ devices, refreshDeviceList }: OtherDevicesProps) {
export function OtherDevices({ devices, refreshDeviceList, verified }: OtherDevicesProps) {
const mx = useMatrixClient();
const crypto = mx.getCrypto();
const [deleted, setDeleted] = useState<Set<string>>(new Set());
const handleToggleDelete = useCallback((deviceId: string) => {
@ -92,6 +97,17 @@ export function OtherDevices({ devices, refreshDeviceList }: OtherDevicesProps)
/>
}
/>
{verified && (
<DeviceVerificationStatus
crypto={crypto}
userId={mx.getSafeUserId()}
deviceId={device.device_id}
>
{(status) =>
status === VerificationStatus.Unverified && <StartVerificationTile />
}
</DeviceVerificationStatus>
)}
</SequenceCard>
))}
</Box>

View file

@ -0,0 +1,40 @@
import React from 'react';
import { Button, Text } from 'folds';
import { SettingTile } from '../../../components/setting-tile';
import * as css from './style.css';
export function ManualVerificationTile() {
return (
<div className={css.UnverifiedCard}>
<SettingTile
after={
<Button size="300" variant="Warning" fill="Soft" radii="300">
<Text size="B300">Verify Manually</Text>
</Button>
}
>
<Text size="L400">Unverified</Text>
<Text size="T200">
Start verification from other device or verify manually with recovery key.
</Text>
</SettingTile>
</div>
);
}
export function StartVerificationTile() {
return (
<div className={css.UnverifiedCard}>
<SettingTile
after={
<Button size="300" variant="Warning" radii="300">
<Text size="B300">Start Verification</Text>
</Button>
}
>
<Text size="L400">Unverified</Text>
<Text size="T200">Verify device identity and grant access to encrypted messages.</Text>
</SettingTile>
</div>
);
}

View file

@ -0,0 +1,12 @@
import { style } from '@vanilla-extract/css';
import { config } from 'folds';
import { ContainerColor } from '../../../styles/ContainerColor.css';
export const UnverifiedCard = style([
ContainerColor({ variant: 'Warning' }),
{
padding: config.space.S200,
borderRadius: config.radii.R300,
borderWidth: config.borderWidth.B300,
},
]);

View file

@ -0,0 +1,31 @@
import { useEffect } from 'react';
import { CryptoApi } from 'matrix-js-sdk/lib/crypto-api';
import { AsyncStatus, useAsyncCallback } from './useAsyncCallback';
import { verifiedDevice } from '../utils/matrix-crypto';
export enum VerificationStatus {
Unknown,
Unverified,
Verified,
Unsupported,
}
export const useDeviceVerificationStatus = (
crypto: CryptoApi | undefined,
userId: string,
deviceId: string | undefined
): VerificationStatus => {
const [verificationState, getVerification] = useAsyncCallback(verifiedDevice);
useEffect(() => {
if (crypto && deviceId) getVerification(crypto, userId, deviceId);
}, [getVerification, userId, crypto, deviceId]);
if (verificationState.status === AsyncStatus.Success) {
if (verificationState.data === null) return VerificationStatus.Unsupported;
return verificationState.data ? VerificationStatus.Verified : VerificationStatus.Unverified;
}
return VerificationStatus.Unknown;
};

View file

@ -0,0 +1,14 @@
import { CryptoApi } from 'matrix-js-sdk/lib/crypto-api';
export const verifiedDevice = async (
api: CryptoApi,
userId: string,
deviceId: string
): Promise<boolean | null> => {
const status = await api.getDeviceVerificationStatus(userId, deviceId);
if (!status) return null;
const verified = status.crossSigningVerified;
return verified;
};