mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-03-13 06:30:01 +01:00
render unverified device verification tile
This commit is contained in:
parent
18357cee95
commit
55248378f4
8 changed files with 159 additions and 3 deletions
24
src/app/components/DeviceVerificationStatus.ts
Normal file
24
src/app/components/DeviceVerificationStatus.ts
Normal 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);
|
||||
}
|
|
@ -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 && (
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
40
src/app/features/settings/devices/Verificaton.tsx
Normal file
40
src/app/features/settings/devices/Verificaton.tsx
Normal 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>
|
||||
);
|
||||
}
|
12
src/app/features/settings/devices/style.css.ts
Normal file
12
src/app/features/settings/devices/style.css.ts
Normal 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,
|
||||
},
|
||||
]);
|
31
src/app/hooks/useDeviceVerificationStatus.ts
Normal file
31
src/app/hooks/useDeviceVerificationStatus.ts
Normal 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;
|
||||
};
|
14
src/app/utils/matrix-crypto.ts
Normal file
14
src/app/utils/matrix-crypto.ts
Normal 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;
|
||||
};
|
Loading…
Reference in a new issue