Passcode: Display error if passcode wasn't created (#3149)

This commit is contained in:
Alexander Zinchuk 2023-05-03 20:21:25 +04:00
parent c8d64dc929
commit cc0e00ad2d
8 changed files with 70 additions and 27 deletions

View File

@ -31,6 +31,7 @@ import { formatDateToString } from '../../../util/dateFormat';
import { setPermanentWebVersion } from '../../../util/permanentWebVersion'; import { setPermanentWebVersion } from '../../../util/permanentWebVersion';
import { clearWebsync } from '../../../util/websync'; import { clearWebsync } from '../../../util/websync';
import { import {
selectCanSetPasscode,
selectCurrentMessageList, selectIsCurrentUserPremium, selectTabState, selectTheme, selectCurrentMessageList, selectIsCurrentUserPremium, selectTabState, selectTheme,
} from '../../../global/selectors'; } from '../../../global/selectors';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
@ -82,7 +83,7 @@ type StateProps =
isConnectionStatusMinimized: ISettings['isConnectionStatusMinimized']; isConnectionStatusMinimized: ISettings['isConnectionStatusMinimized'];
areChatsLoaded?: boolean; areChatsLoaded?: boolean;
hasPasscode?: boolean; hasPasscode?: boolean;
isAuthRememberMe?: boolean; canSetPasscode?: boolean;
} }
& Pick<GlobalState, 'connectionState' | 'isSyncing' | 'archiveSettings'> & Pick<GlobalState, 'connectionState' | 'isSyncing' | 'archiveSettings'>
& Pick<TabState, 'canInstall'>; & Pick<TabState, 'canInstall'>;
@ -114,7 +115,7 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
isConnectionStatusMinimized, isConnectionStatusMinimized,
areChatsLoaded, areChatsLoaded,
hasPasscode, hasPasscode,
isAuthRememberMe, canSetPasscode,
canInstall, canInstall,
archiveSettings, archiveSettings,
}) => { }) => {
@ -158,7 +159,7 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
} }
}, [hasPasscode]); }, [hasPasscode]);
useHotkeys(isAuthRememberMe ? { useHotkeys(canSetPasscode ? {
'Ctrl+Shift+L': handleLockScreenHotkey, 'Ctrl+Shift+L': handleLockScreenHotkey,
'Alt+Shift+L': handleLockScreenHotkey, 'Alt+Shift+L': handleLockScreenHotkey,
'Meta+Shift+L': handleLockScreenHotkey, 'Meta+Shift+L': handleLockScreenHotkey,
@ -481,7 +482,7 @@ export default memo(withGlobal<OwnProps>(
hasPasscode: Boolean(global.passcode.hasPasscode), hasPasscode: Boolean(global.passcode.hasPasscode),
canInstall: Boolean(tabState.canInstall), canInstall: Boolean(tabState.canInstall),
archiveSettings, archiveSettings,
isAuthRememberMe: global.authRememberMe, canSetPasscode: selectCanSetPasscode(global),
}; };
}, },
)(LeftMainHeader)); )(LeftMainHeader));

View File

@ -5,7 +5,7 @@ import { getActions, withGlobal } from '../../../global';
import type { ApiPrivacySettings } from '../../../types'; import type { ApiPrivacySettings } from '../../../types';
import { SettingsScreens } from '../../../types'; import { SettingsScreens } from '../../../types';
import { selectIsCurrentUserPremium } from '../../../global/selectors'; import { selectCanSetPasscode, selectIsCurrentUserPremium } from '../../../global/selectors';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
import useHistoryBack from '../../../hooks/useHistoryBack'; import useHistoryBack from '../../../hooks/useHistoryBack';
@ -23,7 +23,7 @@ type StateProps = {
isCurrentUserPremium?: boolean; isCurrentUserPremium?: boolean;
hasPassword?: boolean; hasPassword?: boolean;
hasPasscode?: boolean; hasPasscode?: boolean;
isAuthRememberMe?: boolean; canSetPasscode?: boolean;
blockedCount: number; blockedCount: number;
webAuthCount: number; webAuthCount: number;
isSensitiveEnabled?: boolean; isSensitiveEnabled?: boolean;
@ -63,7 +63,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
privacyPhoneP2P, privacyPhoneP2P,
onScreenSelect, onScreenSelect,
onReset, onReset,
isAuthRememberMe, canSetPasscode,
}) => { }) => {
const { const {
loadPrivacySettings, loadPrivacySettings,
@ -160,7 +160,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
{lang('BlockedUsers')} {lang('BlockedUsers')}
<span className="settings-item__current-value">{blockedCount || ''}</span> <span className="settings-item__current-value">{blockedCount || ''}</span>
</ListItem> </ListItem>
{isAuthRememberMe && ( {canSetPasscode && (
<ListItem <ListItem
icon="key" icon="key"
narrow narrow
@ -394,7 +394,7 @@ export default memo(withGlobal<OwnProps>(
privacyPhoneCall: privacy.phoneCall, privacyPhoneCall: privacy.phoneCall,
privacyPhoneP2P: privacy.phoneP2P, privacyPhoneP2P: privacy.phoneP2P,
canDisplayChatInTitle, canDisplayChatInTitle,
isAuthRememberMe: global.authRememberMe, canSetPasscode: selectCanSetPasscode(global),
}; };
}, },
)(SettingsPrivacy)); )(SettingsPrivacy));

View File

@ -3,14 +3,15 @@ import { addActionHandler, setGlobal, getGlobal } from '../../index';
import { clearPasscodeSettings, updatePasscodeSettings } from '../../reducers'; import { clearPasscodeSettings, updatePasscodeSettings } from '../../reducers';
import { clearStoredSession, loadStoredSession, storeSession } from '../../../util/sessions'; import { clearStoredSession, loadStoredSession, storeSession } from '../../../util/sessions';
import { import {
clearEncryptedSession, decryptSession, encryptSession, setupPasscode, clearEncryptedSession, decryptSession, encryptSession, forgetPasscode, setupPasscode,
} from '../../../util/passcode'; } from '../../../util/passcode';
import { forceUpdateCache, migrateCache, serializeGlobal } from '../../cache'; import { forceUpdateCache, migrateCache, serializeGlobal } from '../../cache';
import { onBeforeUnload } from '../../../util/schedulers'; import { onBeforeUnload } from '../../../util/schedulers';
import { cloneDeep } from '../../../util/iteratees'; import { cloneDeep } from '../../../util/iteratees';
import { INITIAL_GLOBAL_STATE } from '../../initialState'; import { INITIAL_GLOBAL_STATE } from '../../initialState';
import type { ActionReturnType } from '../../types'; import type { ActionReturnType } from '../../types';
import { signalPasscodeHash } from '../../../util/establishMultitabRole'; import { getCurrentTabId, signalPasscodeHash } from '../../../util/establishMultitabRole';
import { SettingsScreens } from '../../../types';
let noLockOnUnload = false; let noLockOnUnload = false;
onBeforeUnload(() => { onBeforeUnload(() => {
@ -21,7 +22,7 @@ onBeforeUnload(() => {
}); });
addActionHandler('setPasscode', async (global, actions, payload): Promise<void> => { addActionHandler('setPasscode', async (global, actions, payload): Promise<void> => {
const { passcode } = payload; const { passcode, tabId = getCurrentTabId() } = payload;
global = updatePasscodeSettings(global, { global = updatePasscodeSettings(global, {
isLoading: true, isLoading: true,
}); });
@ -36,18 +37,34 @@ addActionHandler('setPasscode', async (global, actions, payload): Promise<void>
isLoading: false, isLoading: false,
})); }));
await encryptSession(sessionJson, globalJson); try {
await encryptSession(sessionJson, globalJson);
signalPasscodeHash(); signalPasscodeHash();
global = getGlobal(); global = getGlobal();
global = updatePasscodeSettings(global, { global = updatePasscodeSettings(global, {
hasPasscode: true, hasPasscode: true,
error: undefined, error: undefined,
isLoading: false, isLoading: false,
}); });
setGlobal(global); setGlobal(global);
forceUpdateCache(true); forceUpdateCache(true);
} catch (err: any) {
forgetPasscode();
global = getGlobal();
global = updatePasscodeSettings(global, {
isLoading: false,
});
setGlobal(global);
actions.showNotification({
message: 'Failed to set passcode',
tabId,
});
actions.requestNextSettingsScreen({ screen: SettingsScreens.PasscodeDisabled, tabId });
}
}); });
addActionHandler('clearPasscode', (global): ActionReturnType => { addActionHandler('clearPasscode', (global): ActionReturnType => {

View File

@ -16,6 +16,7 @@ import { getCurrentTabId, reestablishMasterToSelf } from '../util/establishMulti
import { updateTabState } from './reducers/tabs'; import { updateTabState } from './reducers/tabs';
import type { ActionReturnType, GlobalState } from './types'; import type { ActionReturnType, GlobalState } from './types';
import { isLocalMessageId } from './helpers'; import { isLocalMessageId } from './helpers';
import { isCacheApiSupported } from '../util/cacheApi';
initCache(); initCache();
@ -129,6 +130,12 @@ addActionHandler('init', (global, actions, payload): ActionReturnType => {
actions.initApi(); actions.initApi();
} }
isCacheApiSupported().then((isSupported) => {
global = getGlobal();
global.isCacheApiSupported = isSupported;
setGlobal(global);
});
return updateTabState(global, { return updateTabState(global, {
messageLists: parsedMessageList ? [parsedMessageList] : initialTabState.messageLists, messageLists: parsedMessageList ? [parsedMessageList] : initialTabState.messageLists,
}, tabId); }, tabId);

View File

@ -11,3 +11,7 @@ export function selectNotifyExceptions<T extends GlobalState>(global: T) {
export function selectLanguageCode<T extends GlobalState>(global: T) { export function selectLanguageCode<T extends GlobalState>(global: T) {
return global.settings.byKey.language.replace('-raw', ''); return global.settings.byKey.language.replace('-raw', '');
} }
export function selectCanSetPasscode<T extends GlobalState>(global: T) {
return global.authRememberMe && global.isCacheApiSupported;
}

View File

@ -579,6 +579,7 @@ export type GlobalState = {
appConfig?: ApiAppConfig; appConfig?: ApiAppConfig;
hasWebAuthTokenFailed?: boolean; hasWebAuthTokenFailed?: boolean;
hasWebAuthTokenPasswordRequired?: true; hasWebAuthTokenPasswordRequired?: true;
isCacheApiSupported?: boolean;
connectionState?: ApiUpdateConnectionStateType; connectionState?: ApiUpdateConnectionStateType;
currentUserId?: string; currentUserId?: string;
isSyncing?: boolean; isSyncing?: boolean;
@ -2334,7 +2335,7 @@ export interface ActionPayloads {
connectToActivePhoneCall: undefined; connectToActivePhoneCall: undefined;
// Passcode // Passcode
setPasscode: { passcode: string }; setPasscode: { passcode: string } & WithTabId;
clearPasscode: undefined; clearPasscode: undefined;
lockScreen: undefined; lockScreen: undefined;
decryptSession: { passcode: string }; decryptSession: { passcode: string };

View File

@ -1,6 +1,13 @@
// eslint-disable-next-line no-restricted-globals // eslint-disable-next-line no-restricted-globals
const cacheApi = self.caches; const cacheApi = self.caches;
let isSupported: boolean | undefined;
export async function isCacheApiSupported() {
isSupported = isSupported ?? await cacheApi.has('test').then(() => true).catch(() => false);
return isSupported;
}
export enum Type { export enum Type {
Text, Text,
Blob, Blob,
@ -67,7 +74,7 @@ export async function fetch(
export async function save(cacheName: string, key: string, data: AnyLiteral | Blob | ArrayBuffer | string) { export async function save(cacheName: string, key: string, data: AnyLiteral | Blob | ArrayBuffer | string) {
if (!cacheApi) { if (!cacheApi) {
return undefined; return false;
} }
try { try {
@ -78,11 +85,12 @@ export async function save(cacheName: string, key: string, data: AnyLiteral | Bl
const request = new Request(key.replace(/:/g, '_')); const request = new Request(key.replace(/:/g, '_'));
const response = new Response(cacheData); const response = new Response(cacheData);
const cache = await cacheApi.open(cacheName); const cache = await cacheApi.open(cacheName);
return await cache.put(request, response); await cache.put(request, response);
return true;
} catch (err) { } catch (err) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.warn(err); console.warn(err);
return undefined; return false;
} }
} }

View File

@ -117,7 +117,12 @@ function sha256(plaintext: string) {
} }
async function store(key: string, value: ArrayBuffer) { async function store(key: string, value: ArrayBuffer) {
await cacheApi.save(PASSCODE_CACHE_NAME, key, value); const isSuccessful = await cacheApi.save(PASSCODE_CACHE_NAME, key, value);
if (isSuccessful) {
return;
}
throw new Error('Failed to save to cache');
} }
function load(key: string) { function load(key: string) {