Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

upgrade to rust crypto #2168

Merged
merged 78 commits into from
Feb 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
05859bf
update matrix js sdk
ajbura Aug 17, 2024
2feb4fe
remove dead code
ajbura Aug 17, 2024
b5dbb01
use rust crypto
ajbura Sep 2, 2024
f8053c8
update setPowerLevel usage
ajbura Sep 2, 2024
b36a2a1
fix types
ajbura Sep 2, 2024
0ca778c
Merge branch 'dev' into js-sdk-v34
ajbura Sep 2, 2024
b8f02f5
fix deprecated isRoomEncrypted method uses
ajbura Sep 2, 2024
56840ad
fix deprecated room.currentState uses
ajbura Sep 2, 2024
6835b12
fix deprecated import/export room keys func
ajbura Sep 2, 2024
fe4d0be
Merge branch 'dev' into js-sdk-v34
ajbura Sep 20, 2024
ca3389a
Merge branch 'dev' into js-sdk-v34
ajbura Jan 17, 2025
6bfd6e3
Merge branch 'rework-user-settings' into js-sdk-v34
ajbura Jan 17, 2025
6b77c4d
fix merge issues in image pack file
ajbura Jan 17, 2025
a20092d
fix remaining issues in image pack file
ajbura Jan 17, 2025
4070b97
start indexedDBStore
ajbura Jan 17, 2025
fff4a9c
update package lock and vite-plugin-top-level-await
ajbura Jan 17, 2025
f74f104
user session settings - WIP
ajbura Jan 18, 2025
0816165
add useAsync hook
ajbura Jan 19, 2025
6d4045e
add password stage uia
ajbura Jan 19, 2025
d512fc0
add uia flow matrix error hook
ajbura Jan 19, 2025
a2103a3
add UIA action component
ajbura Jan 19, 2025
e2dbf2e
add options to delete sessions
ajbura Jan 19, 2025
684c928
add sso uia stage
ajbura Jan 19, 2025
3d460bd
fix SSO stage complete error
ajbura Jan 19, 2025
9bc02fd
encryption - WIP
ajbura Jan 20, 2025
7c0f3ce
update user settings encryption terminology
ajbura Jan 24, 2025
aa359b7
add default variant to password input
ajbura Jan 25, 2025
b7a7a37
use password input in uia password stage
ajbura Jan 25, 2025
b1e78cc
add options for local backup in user settings
ajbura Jan 25, 2025
c1cdb97
remove typo in import local backup password input label
ajbura Jan 25, 2025
ee0ee79
online backup - WIP
ajbura Jan 26, 2025
4b3668c
fix uia sso action
ajbura Jan 26, 2025
6def5ab
move access token settings from about to developer tools
ajbura Jan 26, 2025
a50b6ef
merge encryption tab into sessions and rename it to devices
ajbura Jan 26, 2025
5716b75
add device placeholder tile
ajbura Jan 26, 2025
8b85102
add logout dialog
ajbura Jan 28, 2025
d817e11
add logout button for current device
ajbura Jan 28, 2025
18357ce
move other devices in component
ajbura Jan 28, 2025
5524837
render unverified device verification tile
ajbura Jan 29, 2025
64d80a8
add learn more section for current device verification
ajbura Jan 29, 2025
dff90a1
add device verification status badge
ajbura Jan 30, 2025
d1a07c5
add info card component
ajbura Jan 30, 2025
dcfb928
add index file for password input component
ajbura Jan 30, 2025
81fb8c2
add types for secret storage
ajbura Jan 30, 2025
c049dd7
add component to access secret storage key
ajbura Jan 30, 2025
38d0d5f
manual verification - WIP
ajbura Jan 31, 2025
bb81574
update matrix-js-sdk to v35
ajbura Feb 2, 2025
85f0be4
add manual verification
ajbura Feb 2, 2025
091df2a
use react query for device list
ajbura Feb 2, 2025
16b6db6
show unverified tab on sidebar
ajbura Feb 2, 2025
9b9aea5
fix device list updates
ajbura Feb 2, 2025
0127c87
add session key details to current device
ajbura Feb 2, 2025
9513e49
render restore encryption backup
ajbura Feb 2, 2025
91355a2
fix loading state of restore backup
ajbura Feb 2, 2025
9b3b658
fix unverified tab settings closes after verification
ajbura Feb 3, 2025
d759ee4
key backup tile - WIP
ajbura Feb 3, 2025
683b066
fix unverified tab badge
ajbura Feb 3, 2025
88d4a76
rename session key to device key in device tile
ajbura Feb 4, 2025
10fc6ce
improve backup restore functionality
ajbura Feb 4, 2025
3e1f758
fix restore button enabled after layout reload during restoring backup
ajbura Feb 4, 2025
318509e
update backup info on status change
ajbura Feb 4, 2025
c542702
add backup disconnection failures
ajbura Feb 4, 2025
d774ad9
add device verification using sas
ajbura Feb 6, 2025
87b6767
restore backup after verification
ajbura Feb 7, 2025
d6bd62c
show option to logout on startup error screen
ajbura Feb 7, 2025
d0c2890
fix key backup hook update on decryption key cached
ajbura Feb 7, 2025
90f63c1
add option to enable device verification
ajbura Feb 8, 2025
982c738
add device verification reset dialog
ajbura Feb 9, 2025
06f2fb1
add logout button in settings drawer
ajbura Feb 9, 2025
c64e361
add encrypted message lost on logout
ajbura Feb 9, 2025
016d661
fix backup restore never finish with 0 keys
ajbura Feb 9, 2025
e9a8c7c
fix setup dialog hides when enabling device verification
ajbura Feb 9, 2025
0df9ef9
show backup details in menu
ajbura Feb 9, 2025
907e006
update setup device verification body copy
ajbura Feb 9, 2025
01c5287
replace deprecated method
ajbura Feb 9, 2025
513ccf9
fix displayname appear as mxid in settings
ajbura Feb 10, 2025
49950e7
remove old refactored codes
ajbura Feb 10, 2025
bef326e
fix types
ajbura Feb 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7,587 changes: 3,511 additions & 4,076 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
"jotai": "2.6.0",
"linkify-react": "4.1.3",
"linkifyjs": "4.1.3",
"matrix-js-sdk": "34.11.1",
"matrix-js-sdk": "35.0.0",
"millify": "6.1.0",
"pdfjs-dist": "4.2.67",
"prismjs": "1.29.0",
Expand Down Expand Up @@ -108,6 +108,6 @@
"vite": "5.0.13",
"vite-plugin-pwa": "0.20.5",
"vite-plugin-static-copy": "1.0.4",
"vite-plugin-top-level-await": "1.4.1"
"vite-plugin-top-level-await": "1.4.4"
}
}
73 changes: 73 additions & 0 deletions src/app/components/ActionUIA.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React, { ReactNode } from 'react';
import { AuthDict, AuthType, IAuthData, UIAFlow } from 'matrix-js-sdk';
import { getUIAFlowForStages } from '../utils/matrix-uia';
import { useSupportedUIAFlows, useUIACompleted, useUIAFlow } from '../hooks/useUIAFlows';
import { UIAFlowOverlay } from './UIAFlowOverlay';
import { PasswordStage, SSOStage } from './uia-stages';
import { useMatrixClient } from '../hooks/useMatrixClient';

export const SUPPORTED_IN_APP_UIA_STAGES = [AuthType.Password, AuthType.Sso];

export const pickUIAFlow = (uiaFlows: UIAFlow[]): UIAFlow | undefined => {
const passwordFlow = getUIAFlowForStages(uiaFlows, [AuthType.Password]);
if (passwordFlow) return passwordFlow;
return getUIAFlowForStages(uiaFlows, [AuthType.Sso]);
};

type ActionUIAProps = {
authData: IAuthData;
ongoingFlow: UIAFlow;
action: (authDict: AuthDict) => void;
onCancel: () => void;
};
export function ActionUIA({ authData, ongoingFlow, action, onCancel }: ActionUIAProps) {
const mx = useMatrixClient();
const completed = useUIACompleted(authData);
const { getStageToComplete } = useUIAFlow(authData, ongoingFlow);

const stageToComplete = getStageToComplete();

if (!stageToComplete) return null;
return (
<UIAFlowOverlay
currentStep={completed.length + 1}
stepCount={ongoingFlow.stages.length}
onCancel={onCancel}
>
{stageToComplete.type === AuthType.Password && (
<PasswordStage
userId={mx.getUserId()!}
stageData={stageToComplete}
onCancel={onCancel}
submitAuthDict={action}
/>
)}
{stageToComplete.type === AuthType.Sso && stageToComplete.session && (
<SSOStage
ssoRedirectURL={mx.getFallbackAuthUrl(AuthType.Sso, stageToComplete.session)}
stageData={stageToComplete}
onCancel={onCancel}
submitAuthDict={action}
/>
)}
</UIAFlowOverlay>
);
}

type ActionUIAFlowsLoaderProps = {
authData: IAuthData;
unsupported: () => ReactNode;
children: (ongoingFlow: UIAFlow) => ReactNode;
};
export function ActionUIAFlowsLoader({
authData,
unsupported,
children,
}: ActionUIAFlowsLoaderProps) {
const supportedFlows = useSupportedUIAFlows(authData.flows ?? [], SUPPORTED_IN_APP_UIA_STAGES);
const ongoingFlow = supportedFlows.length > 0 ? supportedFlows[0] : undefined;

if (!ongoingFlow) return unsupported();

return children(ongoingFlow);
}
281 changes: 281 additions & 0 deletions src/app/components/BackupRestore.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
import React, { MouseEventHandler, useCallback, useState } from 'react';
import { useAtom } from 'jotai';
import { CryptoApi, KeyBackupInfo } from 'matrix-js-sdk/lib/crypto-api';
import {
Badge,
Box,
Button,
color,
config,
Icon,
IconButton,
Icons,
Menu,
percent,
PopOut,
ProgressBar,
RectCords,
Spinner,
Text,
} from 'folds';
import FocusTrap from 'focus-trap-react';
import { BackupProgressStatus, backupRestoreProgressAtom } from '../state/backupRestore';
import { InfoCard } from './info-card';
import { AsyncStatus, useAsyncCallback } from '../hooks/useAsyncCallback';
import {
useKeyBackupInfo,
useKeyBackupStatus,
useKeyBackupSync,
useKeyBackupTrust,
} from '../hooks/useKeyBackup';
import { stopPropagation } from '../utils/keyboard';
import { useRestoreBackupOnVerification } from '../hooks/useRestoreBackupOnVerification';

type BackupStatusProps = {
enabled: boolean;
};
function BackupStatus({ enabled }: BackupStatusProps) {
return (
<Box as="span" gap="100" alignItems="Center">
<Badge variant={enabled ? 'Success' : 'Critical'} fill="Solid" size="200" radii="Pill" />
<Text
as="span"
size="L400"
style={{ color: enabled ? color.Success.Main : color.Critical.Main }}
>
{enabled ? 'Connected' : 'Disconnected'}
</Text>
</Box>
);
}
type BackupSyncingProps = {
count: number;
};
function BackupSyncing({ count }: BackupSyncingProps) {
return (
<Box as="span" gap="100" alignItems="Center">
<Spinner size="50" variant="Primary" fill="Soft" />
<Text as="span" size="L400" style={{ color: color.Primary.Main }}>
Syncing ({count})
</Text>
</Box>
);
}

function BackupProgressFetching() {
return (
<Box grow="Yes" gap="200" alignItems="Center">
<Badge variant="Secondary" fill="Solid" radii="300">
<Text size="L400">Restoring: 0%</Text>
</Badge>
<Box grow="Yes" direction="Column">
<ProgressBar variant="Secondary" size="300" min={0} max={1} value={0} />
</Box>
<Spinner size="50" variant="Secondary" fill="Soft" />
</Box>
);
}

type BackupProgressProps = {
total: number;
downloaded: number;
};
function BackupProgress({ total, downloaded }: BackupProgressProps) {
return (
<Box grow="Yes" gap="200" alignItems="Center">
<Badge variant="Secondary" fill="Solid" radii="300">
<Text size="L400">Restoring: {`${Math.round(percent(0, total, downloaded))}%`}</Text>
</Badge>
<Box grow="Yes" direction="Column">
<ProgressBar variant="Secondary" size="300" min={0} max={total} value={downloaded} />
</Box>
<Badge variant="Secondary" fill="Soft" radii="Pill">
<Text size="L400">
{downloaded} / {total}
</Text>
</Badge>
</Box>
);
}

type BackupTrustInfoProps = {
crypto: CryptoApi;
backupInfo: KeyBackupInfo;
};
function BackupTrustInfo({ crypto, backupInfo }: BackupTrustInfoProps) {
const trust = useKeyBackupTrust(crypto, backupInfo);

if (!trust) return null;

return (
<Box direction="Column">
{trust.matchesDecryptionKey ? (
<Text size="T200" style={{ color: color.Success.Main }}>
<b>Backup has trusted decryption key.</b>
</Text>
) : (
<Text size="T200" style={{ color: color.Critical.Main }}>
<b>Backup does not have trusted decryption key!</b>
</Text>
)}
{trust.trusted ? (
<Text size="T200" style={{ color: color.Success.Main }}>
<b>Backup has trusted by signature.</b>
</Text>
) : (
<Text size="T200" style={{ color: color.Critical.Main }}>
<b>Backup does not have trusted signature!</b>
</Text>
)}
</Box>
);
}

type BackupRestoreTileProps = {
crypto: CryptoApi;
};
export function BackupRestoreTile({ crypto }: BackupRestoreTileProps) {
const [restoreProgress, setRestoreProgress] = useAtom(backupRestoreProgressAtom);
const restoring =
restoreProgress.status === BackupProgressStatus.Fetching ||
restoreProgress.status === BackupProgressStatus.Loading;

const backupEnabled = useKeyBackupStatus(crypto);
const backupInfo = useKeyBackupInfo(crypto);
const [remainingSession, syncFailure] = useKeyBackupSync();

const [menuCords, setMenuCords] = useState<RectCords>();

const handleMenu: MouseEventHandler<HTMLButtonElement> = (evt) => {
setMenuCords(evt.currentTarget.getBoundingClientRect());
};

const [restoreState, restoreBackup] = useAsyncCallback<void, Error, []>(
useCallback(async () => {
await crypto.restoreKeyBackup({
progressCallback(progress) {
setRestoreProgress(progress);
},
});
}, [crypto, setRestoreProgress])
);

const handleRestore = () => {
setMenuCords(undefined);
restoreBackup();
};

return (
<InfoCard
variant="Surface"
title="Encryption Backup"
after={
<Box alignItems="Center" gap="200">
{remainingSession === 0 ? (
<BackupStatus enabled={backupEnabled} />
) : (
<BackupSyncing count={remainingSession} />
)}
<IconButton
aria-pressed={!!menuCords}
size="300"
variant="Surface"
radii="300"
onClick={handleMenu}
>
<Icon size="100" src={Icons.VerticalDots} />
</IconButton>
<PopOut
anchor={menuCords}
offset={5}
position="Bottom"
align="End"
content={
<FocusTrap
focusTrapOptions={{
initialFocus: false,
onDeactivate: () => setMenuCords(undefined),
clickOutsideDeactivates: true,
isKeyForward: (evt: KeyboardEvent) =>
evt.key === 'ArrowDown' || evt.key === 'ArrowRight',
isKeyBackward: (evt: KeyboardEvent) =>
evt.key === 'ArrowUp' || evt.key === 'ArrowLeft',
escapeDeactivates: stopPropagation,
}}
>
<Menu
style={{
padding: config.space.S100,
}}
>
<Box direction="Column" gap="100">
<Box direction="Column" gap="200">
<InfoCard
variant="SurfaceVariant"
title="Backup Details"
description={
<>
<span>Version: {backupInfo?.version ?? 'NIL'}</span>
<br />
<span>Keys: {backupInfo?.count ?? 'NIL'}</span>
</>
}
/>
</Box>
<Button
size="300"
variant="Success"
radii="300"
aria-disabled={restoreState.status === AsyncStatus.Loading || restoring}
onClick={
restoreState.status === AsyncStatus.Loading || restoring
? undefined
: handleRestore
}
before={<Icon size="100" src={Icons.Download} />}
>
<Text size="B300">Restore Backup</Text>
</Button>
</Box>
</Menu>
</FocusTrap>
}
/>
</Box>
}
>
{syncFailure && (
<Text size="T200" style={{ color: color.Critical.Main }}>
<b>{syncFailure}</b>
</Text>
)}
{!backupEnabled && backupInfo === null && (
<Text size="T200" style={{ color: color.Critical.Main }}>
<b>No backup present on server!</b>
</Text>
)}
{!syncFailure && !backupEnabled && backupInfo && (
<BackupTrustInfo crypto={crypto} backupInfo={backupInfo} />
)}
{restoreState.status === AsyncStatus.Loading && !restoring && <BackupProgressFetching />}
{restoreProgress.status === BackupProgressStatus.Fetching && <BackupProgressFetching />}
{restoreProgress.status === BackupProgressStatus.Loading && (
<BackupProgress
total={restoreProgress.data.total}
downloaded={restoreProgress.data.downloaded}
/>
)}
{restoreState.status === AsyncStatus.Error && (
<Text size="T200" style={{ color: color.Critical.Main }}>
<b>{restoreState.error.message}</b>
</Text>
)}
</InfoCard>
);
}

export function AutoRestoreBackupOnVerification() {
useRestoreBackupOnVerification();

return null;
}
2 changes: 1 addition & 1 deletion src/app/components/CapabilitiesAndMediaConfigLoader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export function CapabilitiesAndMediaConfigLoader({
[]
>(
useCallback(async () => {
const result = await Promise.allSettled([mx.getCapabilities(true), mx.getMediaConfig()]);
const result = await Promise.allSettled([mx.getCapabilities(), mx.getMediaConfig()]);
const capabilities = promiseFulfilledResult(result[0]);
const mediaConfig = promiseFulfilledResult(result[1]);
return [capabilities, mediaConfig];
Expand Down
2 changes: 1 addition & 1 deletion src/app/components/CapabilitiesLoader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ type CapabilitiesLoaderProps = {
export function CapabilitiesLoader({ children }: CapabilitiesLoaderProps) {
const mx = useMatrixClient();

const [state, load] = useAsyncCallback(useCallback(() => mx.getCapabilities(true), [mx]));
const [state, load] = useAsyncCallback(useCallback(() => mx.getCapabilities(), [mx]));

useEffect(() => {
load();
Expand Down
Loading