Page MenuHomePhorge

No OneTemporary

Size
11 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/components/secondary-device-qr-auth-context-provider.react.js b/lib/components/secondary-device-qr-auth-context-provider.react.js
index 84625dc42..052f8892d 100644
--- a/lib/components/secondary-device-qr-auth-context-provider.react.js
+++ b/lib/components/secondary-device-qr-auth-context-provider.react.js
@@ -1,369 +1,385 @@
// @flow
import invariant from 'invariant';
import * as React from 'react';
import { useDebugLogs } from './debug-logs-context.js';
import { setClientDBStoreActionType } from '../actions/client-db-store-actions.js';
import { qrCodeLinkURL } from '../facts/links.js';
import { useSecondaryDeviceLogIn } from '../hooks/login-hooks.js';
import { uintArrayToHexString } from '../media/data-utils.js';
import { isLoggedIn } from '../selectors/user-selectors.js';
import { IdentityClientContext } from '../shared/identity-client-context.js';
import { useTunnelbroker } from '../tunnelbroker/tunnelbroker-context.js';
import { platformToIdentityDeviceType } from '../types/identity-service-types.js';
import type { IdentityAuthResult } from '../types/identity-service-types.js';
import {
type TunnelbrokerToDeviceMessage,
tunnelbrokerToDeviceMessageTypes,
} from '../types/tunnelbroker/messages.js';
import {
peerToPeerMessageTypes,
peerToPeerMessageValidator,
type QRCodeAuthMessage,
} from '../types/tunnelbroker/peer-to-peer-message-types.js';
import {
type QRCodeAuthMessagePayload,
qrCodeAuthMessageTypes,
type QRAuthBackupData,
} from '../types/tunnelbroker/qr-code-auth-message-types.js';
import { getConfig } from '../utils/config.js';
import { getContentSigningKey } from '../utils/crypto-utils.js';
import { getMessageForException } from '../utils/errors.js';
import { useDispatch, useSelector } from '../utils/redux-utils.js';
import { fullBackupSupport } from '../utils/services-utils.js';
-import { waitUntilDatabaseReady } from '../utils/wait-until-db-deleted.js';
+import { waitUntilDatabaseDeleted } from '../utils/wait-until-db-deleted.js';
type Props = {
+children: React.Node,
+onLogInError: (error: mixed) => void,
+generateAESKey: () => Promise<Uint8Array>,
+composeTunnelbrokerQRAuthMessage: (
encryptionKey: string,
payload: QRCodeAuthMessagePayload,
) => Promise<QRCodeAuthMessage>,
+parseTunnelbrokerQRAuthMessage: (
encryptionKey: string,
message: QRCodeAuthMessage,
) => Promise<?QRCodeAuthMessagePayload>,
};
type QRData = ?{ +deviceID: string, +aesKey: string };
type SecondaryDeviceQRAuthContextType = {
+qrData: QRData,
+openSecondaryQRAuth: () => Promise<void>,
+closeSecondaryQRAuth: () => void,
+canGenerateQRs: boolean,
};
const SecondaryDeviceQRAuthContext: React.Context<?SecondaryDeviceQRAuthContextType> =
React.createContext<?SecondaryDeviceQRAuthContextType>({
qrData: null,
openSecondaryQRAuth: async () => {},
closeSecondaryQRAuth: () => {},
canGenerateQRs: true,
});
function SecondaryDeviceQRAuthContextProvider(props: Props): React.Node {
const {
children,
onLogInError,
generateAESKey,
composeTunnelbrokerQRAuthMessage,
parseTunnelbrokerQRAuthMessage,
} = props;
const [primaryDeviceID, setPrimaryDeviceID] = React.useState<?string>();
const [qrData, setQRData] = React.useState<?QRData>();
const [qrAuthFinished, setQRAuthFinished] = React.useState(false);
+
const loggedIn = useSelector(isLoggedIn);
+ const prevLoggedIn = React.useRef(loggedIn);
+
+ const logoutStateResetPromise = React.useRef<?Promise<void>>(null);
+
+ if (prevLoggedIn.current !== loggedIn) {
+ if (!loggedIn) {
+ logoutStateResetPromise.current = (async () => {
+ await logoutStateResetPromise.current;
+ await waitUntilDatabaseDeleted();
+ logoutStateResetPromise.current = null;
+ })();
+ }
+
+ prevLoggedIn.current = loggedIn;
+ }
const {
setUnauthorizedDeviceID,
addListener,
removeListener,
socketState,
sendMessageToDevice,
confirmMessageToTunnelbroker,
} = useTunnelbroker();
const identityContext = React.useContext(IdentityClientContext);
const identityClient = identityContext?.identityClient;
const openSecondaryQRAuth = React.useCallback(async () => {
- await waitUntilDatabaseReady();
+ await logoutStateResetPromise.current;
const [ed25519, rawAESKey] = await Promise.all([
getContentSigningKey(),
generateAESKey(),
]);
const aesKeyAsHexString: string = uintArrayToHexString(rawAESKey);
setUnauthorizedDeviceID(ed25519);
setQRData({ deviceID: ed25519, aesKey: aesKeyAsHexString });
setQRAuthFinished(false);
}, [generateAESKey, setUnauthorizedDeviceID]);
const closeSecondaryQRAuth = React.useCallback(() => {
setUnauthorizedDeviceID(null);
}, [setUnauthorizedDeviceID]);
const logInSecondaryDevice = useSecondaryDeviceLogIn();
const performLogIn = React.useCallback(
async (userID: string): Promise<?IdentityAuthResult> => {
try {
return await logInSecondaryDevice(userID);
} catch (err) {
onLogInError(err);
void openSecondaryQRAuth();
return null;
}
},
[logInSecondaryDevice, onLogInError, openSecondaryQRAuth],
);
React.useEffect(() => {
if (
!qrData ||
!socketState.isAuthorized ||
!primaryDeviceID ||
qrAuthFinished
) {
return;
}
void (async () => {
const message = await composeTunnelbrokerQRAuthMessage(qrData?.aesKey, {
type: qrCodeAuthMessageTypes.SECONDARY_DEVICE_REGISTRATION_SUCCESS,
});
await sendMessageToDevice({
deviceID: primaryDeviceID,
payload: JSON.stringify(message),
});
setQRAuthFinished(true);
setQRData(null);
})();
}, [
sendMessageToDevice,
primaryDeviceID,
qrData,
socketState,
composeTunnelbrokerQRAuthMessage,
qrAuthFinished,
]);
const dispatch = useDispatch();
const { addLog } = useDebugLogs();
const restoreUserData = React.useCallback(
async (
backupData: ?QRAuthBackupData,
identityAuthResult: ?IdentityAuthResult,
) => {
if (!fullBackupSupport) {
return;
}
try {
const { sqliteAPI } = getConfig();
if (!identityAuthResult) {
throw new Error('Missing identityAuthResult');
}
if (!backupData) {
throw new Error('Missing backupData');
}
await sqliteAPI.restoreUserData(backupData, identityAuthResult);
const clientDBStore = await sqliteAPI.getClientDBStore(
identityAuthResult.userID,
);
dispatch({
type: setClientDBStoreActionType,
payload: clientDBStore,
});
} catch (e) {
addLog(
'Error when restoring User Data',
getMessageForException(e) ?? 'unknown error',
);
}
},
[addLog, dispatch],
);
const tunnelbrokerMessageListener = React.useCallback(
async (message: TunnelbrokerToDeviceMessage) => {
invariant(identityClient, 'identity context not set');
if (
!qrData?.aesKey ||
message.type !== tunnelbrokerToDeviceMessageTypes.MESSAGE_TO_DEVICE ||
socketState.isAuthorized ||
loggedIn
) {
return;
}
let innerMessage;
try {
innerMessage = JSON.parse(message.payload);
} catch {
return;
}
if (
!peerToPeerMessageValidator.is(innerMessage) ||
innerMessage.type !== peerToPeerMessageTypes.QR_CODE_AUTH_MESSAGE
) {
return;
}
let qrCodeAuthMessage;
try {
qrCodeAuthMessage = await parseTunnelbrokerQRAuthMessage(
qrData?.aesKey,
innerMessage,
);
} catch (err) {
console.warn('Failed to decrypt Tunnelbroker QR auth message:', err);
return;
}
if (
!qrCodeAuthMessage ||
qrCodeAuthMessage.type !==
qrCodeAuthMessageTypes.DEVICE_LIST_UPDATE_SUCCESS
) {
return;
}
const {
primaryDeviceID: receivedPrimaryDeviceID,
userID,
backupData,
} = qrCodeAuthMessage;
setPrimaryDeviceID(receivedPrimaryDeviceID);
try {
await confirmMessageToTunnelbroker(message.messageID);
} catch (e) {
console.error(
'Error while confirming DEVICE_LIST_UPDATE_SUCCESS',
getMessageForException(e),
);
}
const identityAuthResult = await performLogIn(userID);
await restoreUserData(backupData, identityAuthResult);
setUnauthorizedDeviceID(null);
},
[
identityClient,
qrData?.aesKey,
socketState.isAuthorized,
loggedIn,
performLogIn,
restoreUserData,
setUnauthorizedDeviceID,
parseTunnelbrokerQRAuthMessage,
confirmMessageToTunnelbroker,
],
);
React.useEffect(() => {
if (!qrData?.deviceID || qrAuthFinished) {
return undefined;
}
addListener(tunnelbrokerMessageListener);
return () => {
removeListener(tunnelbrokerMessageListener);
};
}, [
addListener,
removeListener,
tunnelbrokerMessageListener,
qrData?.deviceID,
qrAuthFinished,
]);
const value = React.useMemo(
() => ({
qrData,
openSecondaryQRAuth,
closeSecondaryQRAuth,
canGenerateQRs: !socketState.isAuthorized,
}),
[
qrData,
openSecondaryQRAuth,
closeSecondaryQRAuth,
socketState.isAuthorized,
],
);
return (
<SecondaryDeviceQRAuthContext.Provider value={value}>
{children}
</SecondaryDeviceQRAuthContext.Provider>
);
}
function useSecondaryDeviceQRAuthContext(): SecondaryDeviceQRAuthContextType {
const context = React.useContext(SecondaryDeviceQRAuthContext);
invariant(context, 'SecondaryDeviceQRAuthContext not found');
return context;
}
function useSecondaryDeviceQRAuthURL(): ?string {
const { qrData, openSecondaryQRAuth, closeSecondaryQRAuth, canGenerateQRs } =
useSecondaryDeviceQRAuthContext();
const [attemptNumber, setAttemptNumber] = React.useState(0);
React.useEffect(() => {
if (!canGenerateQRs || qrData) {
return;
}
void (async () => {
try {
console.log('Generating new QR code');
await openSecondaryQRAuth();
} catch (e) {
console.log('Failed to generate QR Code:', e);
setTimeout(() => setAttemptNumber(attemptNumber + 1), 500);
}
})();
}, [
closeSecondaryQRAuth,
canGenerateQRs,
openSecondaryQRAuth,
attemptNumber,
qrData,
]);
React.useEffect(() => {
return closeSecondaryQRAuth;
}, [closeSecondaryQRAuth]);
const { platform } = getConfig().platformDetails;
const qrCodeURL = React.useMemo(() => {
if (!qrData || !canGenerateQRs) {
return undefined;
}
const identityDeviceType = platformToIdentityDeviceType[platform];
return qrCodeLinkURL(qrData.aesKey, qrData.deviceID, identityDeviceType);
}, [canGenerateQRs, platform, qrData]);
React.useEffect(() => {
if (qrCodeURL) {
console.log(`QR Code URL: ${qrCodeURL}`);
}
}, [qrCodeURL]);
return qrCodeURL;
}
export {
SecondaryDeviceQRAuthContextProvider,
useSecondaryDeviceQRAuthContext,
useSecondaryDeviceQRAuthURL,
};

File Metadata

Mime Type
text/x-diff
Expires
Sun, Dec 7, 4:23 PM (6 h, 58 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5839541
Default Alt Text
(11 KB)

Event Timeline