Page MenuHomePhabricator

D14148.id46460.diff
No OneTemporary

D14148.id46460.diff

diff --git a/native/backup/backup-handler.js b/native/backup/backup-handler.js
--- a/native/backup/backup-handler.js
+++ b/native/backup/backup-handler.js
@@ -1,13 +1,28 @@
// @flow
+import invariant from 'invariant';
import * as React from 'react';
+import { setPeerDeviceListsActionType } from 'lib/actions/aux-user-actions.js';
import { createUserKeysBackupActionTypes } from 'lib/actions/backup-actions.js';
+import { useBroadcastDeviceListUpdates } from 'lib/hooks/peer-list-hooks.js';
import { useCheckIfPrimaryDevice } from 'lib/hooks/primary-device-hooks.js';
-import { isLoggedIn } from 'lib/selectors/user-selectors.js';
+import { isLoggedIn, getAllPeerDevices } from 'lib/selectors/user-selectors.js';
+import { signDeviceListUpdate } from 'lib/shared/device-list-utils.js';
+import { IdentityClientContext } from 'lib/shared/identity-client-context.js';
import { useStaffAlert } from 'lib/shared/staff-utils.js';
+import type {
+ RawDeviceList,
+ SignedDeviceList,
+} from 'lib/types/identity-service-types.js';
+import {
+ composeRawDeviceList,
+ rawDeviceListFromSignedList,
+} from 'lib/utils/device-list-utils.js';
import { getMessageForException } from 'lib/utils/errors.js';
import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js';
+import { useDispatch } from 'lib/utils/redux-utils.js';
+import { usingRestoreFlow } from 'lib/utils/services-utils.js';
import { useClientBackup } from './use-client-backup.js';
import { useGetBackupSecretForLoggedInUser } from './use-get-backup-secret.js';
@@ -17,6 +32,30 @@
const millisecondsPerDay = 24 * 60 * 60 * 1000;
+async function reorderAndSignDeviceList(
+ thisDeviceID: string,
+ currentDeviceList: RawDeviceList,
+): Promise<{
+ +rawList: RawDeviceList,
+ +signedList: SignedDeviceList,
+}> {
+ const currentDevices = [...currentDeviceList.devices];
+
+ const thisDeviceIndex = currentDevices.indexOf(thisDeviceID);
+ if (thisDeviceIndex < 0) {
+ throw new Error("Device list doesn't contain current device ID");
+ }
+
+ const newDevices =
+ thisDeviceIndex === 0
+ ? currentDevices
+ : [thisDeviceID, ...currentDevices.splice(thisDeviceIndex, 1)];
+
+ const rawList = composeRawDeviceList(newDevices);
+ const signedList = await signDeviceListUpdate(rawList);
+ return { rawList, signedList };
+}
+
function BackupHandler(): null {
const loggedIn = useSelector(isLoggedIn);
const staffCanSee = useStaffCanSee();
@@ -37,6 +76,15 @@
const backupUploadInProgress = React.useRef<boolean>(false);
const [handlerStarted, setHandlerStarted] = React.useState(false);
+ const identityContext = React.useContext(IdentityClientContext);
+ invariant(identityContext, 'Identity context should be set');
+
+ const migrationRunning = React.useRef(false);
+ const dispatch = useDispatch();
+
+ const broadcastDeviceListUpdates = useBroadcastDeviceListUpdates();
+ const allPeerDevices = useSelector(getAllPeerDevices);
+
React.useEffect(() => {
if (!staffCanSee) {
return;
@@ -141,13 +189,34 @@
void (async () => {
backupUploadInProgress.current = true;
+
+ const { getAuthMetadata, identityClient } = identityContext;
+ const { userID, deviceID } = await getAuthMetadata();
+ if (!userID) {
+ throw new Error('Missing auth metadata');
+ }
+ if (!userIdentifier) {
+ throw new Error('Missing userIdentifier');
+ }
+ const deviceListsResponse = await identityClient.getDeviceListsForUsers([
+ userID,
+ ]);
+ const currentDeviceList =
+ deviceListsResponse.usersSignedDeviceLists[userID];
+ const currentUserPlatformDetails =
+ deviceListsResponse.usersDevicesPlatformDetails[userID];
+ if (!currentDeviceList || !currentUserPlatformDetails) {
+ throw new Error('Device list not found for current user');
+ }
+
+ const deviceListIsSigned = !!currentDeviceList.curPrimarySignature;
const isPrimaryDevice = await checkIfPrimaryDevice();
- if (!isPrimaryDevice) {
+ if (!isPrimaryDevice && deviceListIsSigned) {
backupUploadInProgress.current = false;
return;
}
- if (latestBackupInfo) {
+ if (isPrimaryDevice && latestBackupInfo) {
const timestamp = latestBackupInfo.timestamp;
if (timestamp >= Date.now() - millisecondsPerDay) {
backupUploadInProgress.current = false;
@@ -165,11 +234,75 @@
try {
const promise = (async () => {
- const backupID = await createUserKeysBackup();
- return {
- backupID,
- timestamp: Date.now(),
- };
+ if (usingRestoreFlow && !latestBackupInfo && !deviceListIsSigned) {
+ try {
+ migrationRunning.current = true;
+ if (!userID || !deviceID) {
+ throw new Error('Missing auth metadata');
+ }
+
+ const { updateDeviceList } = identityClient;
+ invariant(
+ updateDeviceList,
+ 'updateDeviceList() should be defined on native. ' +
+ 'Are you calling it on a non-primary device?',
+ );
+
+ // 1. upload UserKeys (without updating the store)
+ const backupID = await createUserKeysBackup();
+
+ // 2. create in-memory device list (reorder and sign)
+ const newDeviceList = await reorderAndSignDeviceList(
+ deviceID,
+ rawDeviceListFromSignedList(currentDeviceList),
+ );
+
+ // 3. UpdateDeviceList RPC transaction
+ await updateDeviceList(newDeviceList.signedList);
+
+ // 4. fetch backupID again and compare
+ const fetchedBackupInfo =
+ await retrieveLatestBackupInfo(userIdentifier);
+ if (!fetchedBackupInfo.backupID !== backupID) {
+ // TODO: - if not equal, upload backup keys again
+ throw new Error('BackupID doesnt match');
+ }
+
+ // 5a. Update self device list
+ if (!userID) {
+ // flow - says userID can be void even despite above check
+ throw new Error('Missing auth metadata');
+ }
+ dispatch({
+ type: setPeerDeviceListsActionType,
+ payload: {
+ deviceLists: { [userID]: newDeviceList.rawList },
+ usersPlatformDetails: {
+ [userID]: currentUserPlatformDetails,
+ },
+ },
+ });
+ // 5b. Broadcast update to peers
+ void broadcastDeviceListUpdates(
+ allPeerDevices.filter(id => id !== deviceID),
+ );
+
+ // 6. Set store value (dispatchActionPromise success return value)
+ return {
+ backupID,
+ timestamp: Date.now(),
+ };
+ } finally {
+ migrationRunning.current = false;
+ backupUploadInProgress.current = false;
+ }
+ } else {
+ const backupID = await createUserKeysBackup();
+ return {
+ backupID,
+ timestamp: Date.now(),
+ };
+ }
})();
void dispatchActionPromise(createUserKeysBackupActionTypes, promise);
await promise;
@@ -181,15 +314,21 @@
backupUploadInProgress.current = false;
})();
}, [
+ allPeerDevices,
+ broadcastDeviceListUpdates,
canPerformBackupOperation,
checkIfPrimaryDevice,
createUserKeysBackup,
+ dispatch,
dispatchActionPromise,
handlerStarted,
+ identityContext,
latestBackupInfo,
+ retrieveLatestBackupInfo,
showAlertToStaff,
staffCanSee,
testUserKeysRestore,
+ userIdentifier,
]);
return null;

File Metadata

Mime Type
text/plain
Expires
Mon, Dec 23, 10:06 AM (9 h, 31 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2694428
Default Alt Text
D14148.id46460.diff (7 KB)

Event Timeline