Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F3515758
D14148.id46460.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
7 KB
Referenced Files
None
Subscribers
None
D14148.id46460.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D14148: [native] Handler for migrating to signed device lists
Attached
Detach File
Event Timeline
Log In to Comment