Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F32158959
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
11 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
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)
Attached To
Mode
rCOMM Comm
Attached
Detach File
Event Timeline
Log In to Comment