-
-
Notifications
You must be signed in to change notification settings - Fork 281
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
redesigned app settings and switch to rust crypto (#1988)
* rework general settings * account settings - WIP * add missing key prop * add object url hook * extract wide modal styles * profile settings and image editor - WIP * add outline style to upload card * remove file param from bind upload atom hook * add compact variant to upload card * add compact upload card renderer * add option to update profile avatar * add option to change profile displayname * allow displayname change based on capabilities check * rearrange settings components into folders * add system notification settings * add initial page param in settings * convert account data hook to typescript * add push rule hook * add notification mode hook * add notification mode switcher component * add all messages notification settings options * add special messages notification settings * add keyword notifications * add ignored users section * improve ignore user list strings * add about settings * add access token option in about settings * add developer tools settings * add expand button to account data dev tool option * update folds * fix editable active element textarea check * do not close dialog when editable element in focus * add text area plugins * add text area intent handler hook * add newline intent mod in text area * add next line hotkey in text area intent hook * add syntax error position dom utility function * add account data editor * add button to send new account data in dev tools * improve custom emoji plugin * add more custom emojis hooks * add text util css * add word break in setting tile title and description * emojis and sticker user settings - WIP * view image packs from settings * emoji pack editing - WIP * add option to edit pack meta * change saved changes message * add image edit and delete controls * add option to upload pack images and apply changes * fix state event type when updating image pack * lazy load pack image tile img * hide upload image button when user can not edit pack * add option to add or remove global image packs * upgrade to rust crypto (#2168) * update matrix js sdk * remove dead code * use rust crypto * update setPowerLevel usage * fix types * fix deprecated isRoomEncrypted method uses * fix deprecated room.currentState uses * fix deprecated import/export room keys func * fix merge issues in image pack file * fix remaining issues in image pack file * start indexedDBStore * update package lock and vite-plugin-top-level-await * user session settings - WIP * add useAsync hook * add password stage uia * add uia flow matrix error hook * add UIA action component * add options to delete sessions * add sso uia stage * fix SSO stage complete error * encryption - WIP * update user settings encryption terminology * add default variant to password input * use password input in uia password stage * add options for local backup in user settings * remove typo in import local backup password input label * online backup - WIP * fix uia sso action * move access token settings from about to developer tools * merge encryption tab into sessions and rename it to devices * add device placeholder tile * add logout dialog * add logout button for current device * move other devices in component * render unverified device verification tile * add learn more section for current device verification * add device verification status badge * add info card component * add index file for password input component * add types for secret storage * add component to access secret storage key * manual verification - WIP * update matrix-js-sdk to v35 * add manual verification * use react query for device list * show unverified tab on sidebar * fix device list updates * add session key details to current device * render restore encryption backup * fix loading state of restore backup * fix unverified tab settings closes after verification * key backup tile - WIP * fix unverified tab badge * rename session key to device key in device tile * improve backup restore functionality * fix restore button enabled after layout reload during restoring backup * update backup info on status change * add backup disconnection failures * add device verification using sas * restore backup after verification * show option to logout on startup error screen * fix key backup hook update on decryption key cached * add option to enable device verification * add device verification reset dialog * add logout button in settings drawer * add encrypted message lost on logout * fix backup restore never finish with 0 keys * fix setup dialog hides when enabling device verification * show backup details in menu * update setup device verification body copy * replace deprecated method * fix displayname appear as mxid in settings * remove old refactored codes * fix types
- Loading branch information
Showing
196 changed files
with
14,274 additions
and
8,506 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.