diff --git a/lib/keyserver-conn/recovery-utils.js b/lib/keyserver-conn/recovery-utils.js new file mode 100644 --- /dev/null +++ b/lib/keyserver-conn/recovery-utils.js @@ -0,0 +1,153 @@ +// @flow + +import { type ActionTypes, setNewSession } from './keyserver-conn-types.js'; +import type { + LogInActionSource, + LogInStartingPayload, + LogInResult, +} from '../types/account-types.js'; +import type { Endpoint } from '../types/endpoints.js'; +import type { Dispatch } from '../types/redux-types.js'; +import type { ClientSessionChange } from '../types/session-types.js'; +import callServerEndpoint from '../utils/call-server-endpoint.js'; +import type { CallServerEndpointOptions } from '../utils/call-server-endpoint.js'; +import { getConfig } from '../utils/config.js'; +import { promiseAll } from '../utils/promises.js'; +import { wrapActionPromise } from '../utils/redux-promise-utils.js'; +import { usingCommServicesAccessToken } from '../utils/services-utils.js'; + +// This function is a shortcut that tells us whether it's worth even trying to +// call resolveKeyserverSessionInvalidation +function canResolveKeyserverSessionInvalidation(): boolean { + if (usingCommServicesAccessToken) { + // We can always try to resolve a keyserver session invalidation + // automatically using the Olm auth responder + return true; + } + const { resolveKeyserverSessionInvalidationUsingNativeCredentials } = + getConfig(); + // If we can't use the Olm auth responder, then we can only resolve a + // keyserver session invalidation on native, where we have access to the + // user's native credentials. Note that we can't do this for ETH users, but we + // don't know if the user is an ETH user from this function + return !!resolveKeyserverSessionInvalidationUsingNativeCredentials; +} + +// This function attempts to resolve an invalid keyserver session. A session can +// become invalid when a keyserver invalidates it, or due to inconsistent client +// state. If the client is usingCommServicesAccessToken, then the invalidation +// recovery will try to go through the keyserver's Olm auth responder. +// Otherwise, it will attempt to use the user's credentials to log in with the +// legacy auth responder, which won't work on web and won't work for ETH users. +async function resolveKeyserverSessionInvalidation( + dispatch: Dispatch, + cookie: ?string, + urlPrefix: string, + logInActionSource: LogInActionSource, + keyserverID: string, + getInitialNotificationsEncryptedMessage?: () => Promise, +): Promise { + const { resolveKeyserverSessionInvalidationUsingNativeCredentials } = + getConfig(); + if (!resolveKeyserverSessionInvalidationUsingNativeCredentials) { + return null; + } + let newSessionChange = null; + let callServerEndpointCallback = null; + const boundCallServerEndpoint = async ( + endpoint: Endpoint, + data: { +[key: string]: mixed }, + options?: ?CallServerEndpointOptions, + ) => { + const innerBoundSetNewSession = ( + sessionChange: ClientSessionChange, + error: ?string, + ) => { + newSessionChange = sessionChange; + setNewSession( + dispatch, + sessionChange, + null, + error, + logInActionSource, + keyserverID, + ); + }; + try { + const result = await callServerEndpoint( + cookie, + innerBoundSetNewSession, + () => new Promise(r => r(null)), + () => new Promise(r => r(null)), + urlPrefix, + null, + false, + null, + null, + endpoint, + data, + dispatch, + options, + false, + keyserverID, + ); + if (callServerEndpointCallback) { + callServerEndpointCallback(!!newSessionChange); + } + return result; + } catch (e) { + if (callServerEndpointCallback) { + callServerEndpointCallback(!!newSessionChange); + } + throw e; + } + }; + + const boundCallKeyserverEndpoint = ( + endpoint: Endpoint, + requests: { +[keyserverID: string]: ?{ +[string]: mixed } }, + options?: ?CallServerEndpointOptions, + ) => { + if (requests[keyserverID]) { + const promises = { + [keyserverID]: boundCallServerEndpoint( + endpoint, + requests[keyserverID], + options, + ), + }; + return promiseAll(promises); + } + return Promise.resolve({}); + }; + + const dispatchRecoveryAttempt = ( + actionTypes: ActionTypes< + 'LOG_IN_STARTED', + 'LOG_IN_SUCCESS', + 'LOG_IN_FAILED', + >, + promise: Promise, + inputStartingPayload: LogInStartingPayload, + ) => { + const startingPayload = { ...inputStartingPayload, logInActionSource }; + void dispatch( + wrapActionPromise(actionTypes, promise, null, startingPayload), + ); + return new Promise(r => (callServerEndpointCallback = r)); + }; + await resolveKeyserverSessionInvalidationUsingNativeCredentials( + boundCallServerEndpoint, + boundCallKeyserverEndpoint, + dispatchRecoveryAttempt, + logInActionSource, + keyserverID, + getInitialNotificationsEncryptedMessage, + ); + return newSessionChange; +} + +export { + canResolveKeyserverSessionInvalidation, + resolveKeyserverSessionInvalidation, +}; diff --git a/lib/socket/socket.react.js b/lib/socket/socket.react.js --- a/lib/socket/socket.react.js +++ b/lib/socket/socket.react.js @@ -16,6 +16,7 @@ import { updateActivityActionTypes } from '../actions/activity-actions.js'; import { updateLastCommunicatedPlatformDetailsActionType } from '../actions/device-actions.js'; import { setNewSessionActionType } from '../keyserver-conn/keyserver-conn-types.js'; +import { resolveKeyserverSessionInvalidation } from '../keyserver-conn/recovery-utils.js'; import { unsupervisedBackgroundActionType } from '../reducers/lifecycle-state-reducer.js'; import { pingFrequency, @@ -65,7 +66,6 @@ type PongServerSocketMessage, } from '../types/socket-types.js'; import { actionLogger } from '../utils/action-logger.js'; -import { resolveKeyserverSessionInvalidation } from '../utils/action-utils.js'; import { getConfig } from '../utils/config.js'; import { ServerError, SocketTimeout, SocketOffline } from '../utils/errors.js'; import { promiseAll } from '../utils/promises.js'; diff --git a/lib/utils/action-utils.js b/lib/utils/action-utils.js --- a/lib/utils/action-utils.js +++ b/lib/utils/action-utils.js @@ -10,23 +10,15 @@ CallServerEndpoint, CallServerEndpointOptions, } from './call-server-endpoint.js'; -import { getConfig } from './config.js'; -import { promiseAll } from './promises.js'; -import { wrapActionPromise } from './redux-promise-utils.js'; import { useSelector, useDispatch } from './redux-utils.js'; -import { usingCommServicesAccessToken } from './services-utils.js'; import { ashoatKeyserverID } from './validation-utils.js'; +import { setNewSession } from '../keyserver-conn/keyserver-conn-types.js'; import { - type ActionTypes, - setNewSession, -} from '../keyserver-conn/keyserver-conn-types.js'; + canResolveKeyserverSessionInvalidation, + resolveKeyserverSessionInvalidation, +} from '../keyserver-conn/recovery-utils.js'; import { serverCallStateSelector } from '../selectors/server-calls.js'; -import { - logInActionSources, - type LogInActionSource, - type LogInStartingPayload, - type LogInResult, -} from '../types/account-types.js'; +import { logInActionSources } from '../types/account-types.js'; import type { PlatformDetails } from '../types/device-types.js'; import type { Endpoint, SocketAPIHandler } from '../types/endpoints.js'; import type { Dispatch } from '../types/redux-types.js'; @@ -38,137 +30,6 @@ callServerEndpoint: ?CallServerEndpoint, ) => void)[] = []; -// This function is a shortcut that tells us whether it's worth even trying to -// call resolveKeyserverSessionInvalidation -function canResolveKeyserverSessionInvalidation() { - if (usingCommServicesAccessToken) { - // We can always try to resolve a keyserver session invalidation - // automatically using the Olm auth responder - return true; - } - const { resolveKeyserverSessionInvalidationUsingNativeCredentials } = - getConfig(); - // If we can't use the Olm auth responder, then we can only resolve a - // keyserver session invalidation on native, where we have access to the - // user's native credentials. Note that we can't do this for ETH users, but we - // don't know if the user is an ETH user from this function - return !!resolveKeyserverSessionInvalidationUsingNativeCredentials; -} - -// This function attempts to resolve an invalid keyserver session. A session can -// become invalid when a keyserver invalidates it, or due to inconsistent client -// state. If the client is usingCommServicesAccessToken, then the invalidation -// recovery will try to go through the keyserver's Olm auth responder. -// Otherwise, it will attempt to use the user's credentials to log in with the -// legacy auth responder, which won't work on web and won't work for ETH users. -async function resolveKeyserverSessionInvalidation( - dispatch: Dispatch, - cookie: ?string, - urlPrefix: string, - logInActionSource: LogInActionSource, - keyserverID: string, - getInitialNotificationsEncryptedMessage?: () => Promise, -): Promise { - const { resolveKeyserverSessionInvalidationUsingNativeCredentials } = - getConfig(); - if (!resolveKeyserverSessionInvalidationUsingNativeCredentials) { - return null; - } - let newSessionChange = null; - let callServerEndpointCallback = null; - const boundCallServerEndpoint = async ( - endpoint: Endpoint, - data: { +[key: string]: mixed }, - options?: ?CallServerEndpointOptions, - ) => { - const innerBoundSetNewSession = ( - sessionChange: ClientSessionChange, - error: ?string, - ) => { - newSessionChange = sessionChange; - setNewSession( - dispatch, - sessionChange, - null, - error, - logInActionSource, - keyserverID, - ); - }; - try { - const result = await callServerEndpoint( - cookie, - innerBoundSetNewSession, - () => new Promise(r => r(null)), - () => new Promise(r => r(null)), - urlPrefix, - null, - false, - null, - null, - endpoint, - data, - dispatch, - options, - false, - keyserverID, - ); - if (callServerEndpointCallback) { - callServerEndpointCallback(!!newSessionChange); - } - return result; - } catch (e) { - if (callServerEndpointCallback) { - callServerEndpointCallback(!!newSessionChange); - } - throw e; - } - }; - - const boundCallKeyserverEndpoint = ( - endpoint: Endpoint, - requests: { +[keyserverID: string]: ?{ +[string]: mixed } }, - options?: ?CallServerEndpointOptions, - ) => { - if (requests[keyserverID]) { - const promises = { - [keyserverID]: boundCallServerEndpoint( - endpoint, - requests[keyserverID], - options, - ), - }; - return promiseAll(promises); - } - return Promise.resolve({}); - }; - - const dispatchRecoveryAttempt = ( - actionTypes: ActionTypes< - 'LOG_IN_STARTED', - 'LOG_IN_SUCCESS', - 'LOG_IN_FAILED', - >, - promise: Promise, - inputStartingPayload: LogInStartingPayload, - ) => { - const startingPayload = { ...inputStartingPayload, logInActionSource }; - void dispatch( - wrapActionPromise(actionTypes, promise, null, startingPayload), - ); - return new Promise(r => (callServerEndpointCallback = r)); - }; - await resolveKeyserverSessionInvalidationUsingNativeCredentials( - boundCallServerEndpoint, - boundCallKeyserverEndpoint, - dispatchRecoveryAttempt, - logInActionSource, - keyserverID, - getInitialNotificationsEncryptedMessage, - ); - return newSessionChange; -} - // Third param is optional and gets called with newCookie if we get a new cookie // Necessary to propagate cookie in cookieInvalidationRecovery below function bindCookieAndUtilsIntoCallServerEndpoint( @@ -394,7 +255,6 @@ } export { - resolveKeyserverSessionInvalidation, createBoundServerCallsSelector, registerActiveSocket, useServerCall, diff --git a/native/account/logged-out-modal.react.js b/native/account/logged-out-modal.react.js --- a/native/account/logged-out-modal.react.js +++ b/native/account/logged-out-modal.react.js @@ -18,6 +18,7 @@ import { SafeAreaView } from 'react-native-safe-area-context'; import { resetUserStateActionType } from 'lib/actions/user-actions.js'; +import { resolveKeyserverSessionInvalidation } from 'lib/keyserver-conn/recovery-utils.js'; import { cookieSelector, urlPrefixSelector, @@ -26,7 +27,6 @@ import { useInitialNotificationsEncryptedMessage } from 'lib/shared/crypto-utils.js'; import { logInActionSources } from 'lib/types/account-types.js'; import type { Dispatch } from 'lib/types/redux-types.js'; -import { resolveKeyserverSessionInvalidation } from 'lib/utils/action-utils.js'; import { useDispatch } from 'lib/utils/redux-utils.js'; import { usingCommServicesAccessToken } from 'lib/utils/services-utils.js'; import { ashoatKeyserverID } from 'lib/utils/validation-utils.js'; diff --git a/native/data/sqlite-data-handler.js b/native/data/sqlite-data-handler.js --- a/native/data/sqlite-data-handler.js +++ b/native/data/sqlite-data-handler.js @@ -5,6 +5,7 @@ import { setClientDBStoreActionType } from 'lib/actions/client-db-store-actions.js'; import { MediaCacheContext } from 'lib/components/media-cache-provider.react.js'; +import { resolveKeyserverSessionInvalidation } from 'lib/keyserver-conn/recovery-utils.js'; import { reportStoreOpsHandlers } from 'lib/ops/report-store-ops.js'; import { threadStoreOpsHandlers } from 'lib/ops/thread-store-ops.js'; import { userStoreOpsHandlers } from 'lib/ops/user-store-ops.js'; @@ -18,7 +19,6 @@ logInActionSources, type LogInActionSource, } from 'lib/types/account-types.js'; -import { resolveKeyserverSessionInvalidation } from 'lib/utils/action-utils.js'; import { getMessageForException } from 'lib/utils/errors.js'; import { useDispatch } from 'lib/utils/redux-utils.js'; import { ashoatKeyserverID } from 'lib/utils/validation-utils.js'; diff --git a/native/socket.react.js b/native/socket.react.js --- a/native/socket.react.js +++ b/native/socket.react.js @@ -3,6 +3,7 @@ import invariant from 'invariant'; import * as React from 'react'; +import { resolveKeyserverSessionInvalidation } from 'lib/keyserver-conn/recovery-utils.js'; import { preRequestUserStateForSingleKeyserverSelector } from 'lib/selectors/account-selectors.js'; import { cookieSelector, @@ -16,7 +17,6 @@ import Socket, { type BaseSocketProps } from 'lib/socket/socket.react.js'; import { logInActionSources } from 'lib/types/account-types.js'; import { setConnectionIssueActionType } from 'lib/types/socket-types.js'; -import { resolveKeyserverSessionInvalidation } from 'lib/utils/action-utils.js'; import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js'; import { useDispatch } from 'lib/utils/redux-utils.js'; import { ashoatKeyserverID } from 'lib/utils/validation-utils.js';