diff --git a/lib/types/account-types.js b/lib/types/account-types.js index 3e98c6d9d..db40432fc 100644 --- a/lib/types/account-types.js +++ b/lib/types/account-types.js @@ -1,187 +1,188 @@ // @flow import type { PolicyType } from '../facts/policies.js'; import { values } from '../utils/objects'; import type { PlatformDetails } from './device-types'; import type { CalendarQuery, CalendarResult, RawEntryInfo, } from './entry-types'; import type { RawMessageInfo, MessageTruncationStatuses, GenericMessagesResult, } from './message-types'; import type { PreRequestUserState } from './session-types'; import type { RawThreadInfo } from './thread-types'; import type { UserInfo, LoggedOutUserInfo, LoggedInUserInfo, OldLoggedInUserInfo, } from './user-types'; export type ResetPasswordRequest = { +usernameOrEmail: string, }; export type LogOutResult = { +currentUserInfo: ?LoggedOutUserInfo, +preRequestUserState: PreRequestUserState, }; export type LogOutResponse = { +currentUserInfo: LoggedOutUserInfo, }; export type RegisterInfo = { ...LogInExtraInfo, +username: string, +password: string, }; type DeviceTokenUpdateRequest = { +deviceToken: string, }; export type RegisterRequest = { +username: string, +password: string, +calendarQuery?: ?CalendarQuery, +deviceTokenUpdateRequest?: ?DeviceTokenUpdateRequest, +platformDetails: PlatformDetails, }; export type RegisterResponse = { id: string, rawMessageInfos: $ReadOnlyArray, currentUserInfo: OldLoggedInUserInfo | LoggedInUserInfo, cookieChange: { threadInfos: { +[id: string]: RawThreadInfo }, userInfos: $ReadOnlyArray, }, }; export type RegisterResult = { +currentUserInfo: LoggedInUserInfo, +rawMessageInfos: $ReadOnlyArray, +threadInfos: { +[id: string]: RawThreadInfo }, +userInfos: $ReadOnlyArray, +calendarQuery: CalendarQuery, }; export type DeleteAccountRequest = { +password: ?string, }; export const logInActionSources = Object.freeze({ cookieInvalidationResolutionAttempt: 'COOKIE_INVALIDATION_RESOLUTION_ATTEMPT', appStartCookieLoggedInButInvalidRedux: 'APP_START_COOKIE_LOGGED_IN_BUT_INVALID_REDUX', appStartReduxLoggedInButInvalidCookie: 'APP_START_REDUX_LOGGED_IN_BUT_INVALID_COOKIE', socketAuthErrorResolutionAttempt: 'SOCKET_AUTH_ERROR_RESOLUTION_ATTEMPT', sqliteOpFailure: 'SQLITE_OP_FAILURE', sqliteLoadFailure: 'SQLITE_LOAD_FAILURE', logInFromWebForm: 'LOG_IN_FROM_WEB_FORM', logInFromNativeForm: 'LOG_IN_FROM_NATIVE_FORM', logInFromNativeSIWE: 'LOG_IN_FROM_NATIVE_SIWE', + corruptedDatabaseDeletion: 'CORRUPTED_DATABASE_DELETION', refetchUserDataAfterAcknowledgment: 'REFETCH_USER_DATA_AFTER_ACKNOWLEDGMENT', }); export type LogInActionSource = $Values; export type LogInStartingPayload = { +calendarQuery: CalendarQuery, +logInActionSource?: LogInActionSource, }; export type LogInExtraInfo = { +calendarQuery: CalendarQuery, +deviceTokenUpdateRequest?: ?DeviceTokenUpdateRequest, }; export type LogInInfo = { ...LogInExtraInfo, +username: string, +password: string, +logInActionSource: LogInActionSource, }; export type LogInRequest = { +usernameOrEmail?: ?string, +username?: ?string, +password: string, +calendarQuery?: ?CalendarQuery, +deviceTokenUpdateRequest?: ?DeviceTokenUpdateRequest, +platformDetails: PlatformDetails, +watchedIDs: $ReadOnlyArray, +source?: LogInActionSource, }; export type LogInResponse = { +currentUserInfo: LoggedInUserInfo | OldLoggedInUserInfo, +rawMessageInfos: $ReadOnlyArray, +truncationStatuses: MessageTruncationStatuses, +userInfos: $ReadOnlyArray, +rawEntryInfos?: ?$ReadOnlyArray, +serverTime: number, +cookieChange: { +threadInfos: { +[id: string]: RawThreadInfo }, +userInfos: $ReadOnlyArray, }, +notAcknowledgedPolicies?: $ReadOnlyArray, }; export type LogInResult = { +threadInfos: { +[id: string]: RawThreadInfo }, +currentUserInfo: LoggedInUserInfo, +messagesResult: GenericMessagesResult, +userInfos: $ReadOnlyArray, +calendarResult: CalendarResult, +updatesCurrentAsOf: number, +logInActionSource: LogInActionSource, +notAcknowledgedPolicies?: $ReadOnlyArray, }; export type UpdatePasswordRequest = { code: string, password: string, calendarQuery?: ?CalendarQuery, deviceTokenUpdateRequest?: ?DeviceTokenUpdateRequest, platformDetails: PlatformDetails, watchedIDs: $ReadOnlyArray, }; export type PolicyAcknowledgmentRequest = { +policy: PolicyType, }; export type EmailSubscriptionRequest = { +email: string, }; export type UpdateUserSettingsRequest = { +name: 'default_user_notifications', +data: NotificationTypes, }; export const userSettingsTypes = Object.freeze({ DEFAULT_NOTIFICATIONS: 'default_user_notifications', }); export type DefaultNotificationPayload = { +default_user_notifications: ?NotificationTypes, }; export const notificationTypes = Object.freeze({ FOCUSED: 'focused', BADGE_ONLY: 'badge_only', BACKGROUND: 'background', }); export type NotificationTypes = $Values; export const notificationTypeValues: $ReadOnlyArray = values( notificationTypes, ); diff --git a/native/data/sqlite-data-handler.js b/native/data/sqlite-data-handler.js index c4454ebaa..0065f2637 100644 --- a/native/data/sqlite-data-handler.js +++ b/native/data/sqlite-data-handler.js @@ -1,158 +1,201 @@ // @flow import * as React from 'react'; import { Alert } from 'react-native'; import ExitApp from 'react-native-exit-app'; import { useDispatch } from 'react-redux'; import { setClientDBStoreActionType } from 'lib/actions/client-db-store-actions'; import { isLoggedIn } from 'lib/selectors/user-selectors'; -import { logInActionSources } from 'lib/types/account-types'; +import { + logInActionSources, + type LogInActionSource, +} from 'lib/types/account-types'; import { fetchNewCookieFromNativeCredentials } from 'lib/utils/action-utils'; import { getMessageForException } from 'lib/utils/errors'; import { convertClientDBThreadInfosToRawThreadInfos } from 'lib/utils/thread-ops-utils'; import { commCoreModule } from '../native-modules'; import { setStoreLoadedActionType } from '../redux/action-types'; import { useSelector } from '../redux/redux-utils'; import { StaffContext } from '../staff/staff-context'; import { isTaskCancelledError } from '../utils/error-handling'; import { useStaffCanSee } from '../utils/staff-utils'; function SQLiteDataHandler(): React.Node { const storeLoaded = useSelector(state => state.storeLoaded); const dispatch = useDispatch(); const rehydrateConcluded = useSelector( state => !!(state._persist && state._persist.rehydrated), ); const cookie = useSelector(state => state.cookie); const urlPrefix = useSelector(state => state.urlPrefix); const staffCanSee = useStaffCanSee(); const { staffUserHasBeenLoggedIn } = React.useContext(StaffContext); const loggedIn = useSelector(isLoggedIn); const currentLoggedInUserID = useSelector(state => state.currentUserInfo?.anonymous ? undefined : state.currentUserInfo?.id, ); + const callFetchNewCookieFromNativeCredentials = React.useCallback( + async (source: LogInActionSource) => { + try { + await fetchNewCookieFromNativeCredentials( + dispatch, + cookie, + urlPrefix, + source, + ); + dispatch({ type: setStoreLoadedActionType }); + } catch (fetchCookieException) { + if (staffCanSee) { + Alert.alert( + `Error fetching new cookie from native credentials: ${ + getMessageForException(fetchCookieException) ?? + '{no exception message}' + }. Please kill the app.`, + ); + } else { + ExitApp.exitApp(); + } + } + }, + [cookie, dispatch, staffCanSee, urlPrefix], + ); + + const callClearSensitiveData = React.useCallback( + async (triggeredBy: string) => { + if (staffCanSee || staffUserHasBeenLoggedIn) { + Alert.alert('Starting SQLite database deletion process'); + } + await commCoreModule.clearSensitiveData(); + if (staffCanSee || staffUserHasBeenLoggedIn) { + Alert.alert( + 'SQLite database successfully deleted', + `SQLite database deletion was triggered by ${triggeredBy}`, + ); + } + }, + [staffCanSee, staffUserHasBeenLoggedIn], + ); + const handleSensitiveData = React.useCallback(async () => { try { const databaseCurrentUserInfoID = await commCoreModule.getCurrentUserID(); if ( databaseCurrentUserInfoID && databaseCurrentUserInfoID !== currentLoggedInUserID ) { - if (staffCanSee || staffUserHasBeenLoggedIn) { - Alert.alert('Starting SQLite database deletion process'); - } - await commCoreModule.clearSensitiveData(); - if (staffCanSee || staffUserHasBeenLoggedIn) { - Alert.alert( - 'SQLite database successfully deleted', - 'SQLite database deletion was triggered by change in logged-in user credentials', - ); - } + await callClearSensitiveData('change in logged-in user credentials'); } if (currentLoggedInUserID) { await commCoreModule.setCurrentUserID(currentLoggedInUserID); } const databaseDeviceID = await commCoreModule.getDeviceID(); if (!databaseDeviceID) { await commCoreModule.setDeviceID('MOBILE'); } } catch (e) { if (isTaskCancelledError(e)) { return; } if (__DEV__) { throw e; } else { console.log(e); ExitApp.exitApp(); } } - }, [currentLoggedInUserID, staffCanSee, staffUserHasBeenLoggedIn]); + }, [callClearSensitiveData, currentLoggedInUserID]); React.useEffect(() => { if (!rehydrateConcluded) { return; } + + const databaseNeedsDeletion = commCoreModule.checkIfDatabaseNeedsDeletion(); + if (databaseNeedsDeletion) { + (async () => { + try { + await callClearSensitiveData('detecting corrupted database'); + } catch (e) { + if (__DEV__) { + throw e; + } else { + console.log(e); + ExitApp.exitApp(); + } + } + await callFetchNewCookieFromNativeCredentials( + logInActionSources.corruptedDatabaseDeletion, + ); + })(); + return; + } + const sensitiveDataHandled = handleSensitiveData(); if (storeLoaded) { return; } if (!loggedIn) { dispatch({ type: setStoreLoadedActionType }); return; } (async () => { await sensitiveDataHandled; try { const { threads, messages, drafts, } = await commCoreModule.getClientDBStore(); const threadInfosFromDB = convertClientDBThreadInfosToRawThreadInfos( threads, ); dispatch({ type: setClientDBStoreActionType, payload: { drafts, messages, threadStore: { threadInfos: threadInfosFromDB }, currentUserID: currentLoggedInUserID, }, }); } catch (setStoreException) { if (isTaskCancelledError(setStoreException)) { dispatch({ type: setStoreLoadedActionType }); return; } if (staffCanSee) { Alert.alert( `Error setting threadStore or messageStore: ${ getMessageForException(setStoreException) ?? '{no exception message}' }`, ); } - try { - await fetchNewCookieFromNativeCredentials( - dispatch, - cookie, - urlPrefix, - logInActionSources.sqliteLoadFailure, - ); - dispatch({ type: setStoreLoadedActionType }); - } catch (fetchCookieException) { - if (staffCanSee) { - Alert.alert( - `Error fetching new cookie from native credentials: ${ - getMessageForException(fetchCookieException) ?? - '{no exception message}' - }. Please kill the app.`, - ); - } else { - ExitApp.exitApp(); - } - } + await callFetchNewCookieFromNativeCredentials( + logInActionSources.sqliteLoadFailure, + ); } })(); }, [ currentLoggedInUserID, handleSensitiveData, loggedIn, cookie, dispatch, rehydrateConcluded, staffCanSee, storeLoaded, urlPrefix, + staffUserHasBeenLoggedIn, + callFetchNewCookieFromNativeCredentials, + callClearSensitiveData, ]); return null; } export { SQLiteDataHandler };