-
-
Notifications
You must be signed in to change notification settings - Fork 282
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 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
105 changed files
with
7,703 additions
and
7,940 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.