Passcode: Use more persistent storage (#4761)
This commit is contained in:
parent
aad2ed366d
commit
ae1a6da2ec
@ -23,6 +23,7 @@ type RequestStates = {
|
||||
|
||||
const HEALTH_CHECK_TIMEOUT = 150;
|
||||
const HEALTH_CHECK_MIN_DELAY = 5 * 1000; // 5 sec
|
||||
const NO_QUEUE_BEFORE_INIT = new Set(['destroy']);
|
||||
|
||||
let worker: Worker | undefined;
|
||||
const requestStates = new Map<string, RequestStates>();
|
||||
@ -138,6 +139,10 @@ export function setShouldEnableDebugLog(value: boolean) {
|
||||
*/
|
||||
export function callApiLocal<T extends keyof Methods>(fnName: T, ...args: MethodArgs<T>) {
|
||||
if (!isInited) {
|
||||
if (NO_QUEUE_BEFORE_INIT.has(fnName)) {
|
||||
return Promise.resolve(undefined) as MethodResponse<T>;
|
||||
}
|
||||
|
||||
const deferred = new Deferred();
|
||||
localApiRequestsQueue.push({ fnName, args, deferred });
|
||||
|
||||
@ -178,6 +183,10 @@ export function callApiLocal<T extends keyof Methods>(fnName: T, ...args: Method
|
||||
|
||||
export function callApi<T extends keyof Methods>(fnName: T, ...args: MethodArgs<T>) {
|
||||
if (!isInited && isMasterTab) {
|
||||
if (NO_QUEUE_BEFORE_INIT.has(fnName)) {
|
||||
return Promise.resolve(undefined) as MethodResponse<T>;
|
||||
}
|
||||
|
||||
const deferred = new Deferred();
|
||||
apiRequestsQueue.push({ fnName, args, deferred });
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ import { getActions, withGlobal } from '../../global';
|
||||
|
||||
import type { GlobalState } from '../../global/types';
|
||||
|
||||
import { decryptSession } from '../../util/passcode';
|
||||
import { decryptSession, UnrecoverablePasscodeError } from '../../util/passcode';
|
||||
import { LOCAL_TGS_URLS } from '../common/helpers/animatedAssets';
|
||||
|
||||
import useTimeout from '../../hooks/schedulers/useTimeout';
|
||||
@ -70,7 +70,11 @@ const LockScreen: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
|
||||
setValidationError('');
|
||||
decryptSession(passcode).then(unlockScreen, () => {
|
||||
decryptSession(passcode).then(unlockScreen, (err) => {
|
||||
if (err instanceof UnrecoverablePasscodeError) {
|
||||
signOut({ forceInitApi: true });
|
||||
}
|
||||
|
||||
logInvalidUnlockAttempt();
|
||||
setValidationError(lang('lng_passcode_wrong'));
|
||||
});
|
||||
|
||||
@ -33,7 +33,7 @@ export const INACTIVE_MARKER = '[Inactive]';
|
||||
export const DEBUG_PAYMENT_SMART_GLOCAL = false;
|
||||
|
||||
export const SESSION_USER_KEY = 'user_auth';
|
||||
export const PASSCODE_CACHE_NAME = 'tt-passcode';
|
||||
export const LEGACY_PASSCODE_CACHE_NAME = 'tt-passcode';
|
||||
|
||||
export const GLOBAL_STATE_CACHE_DISABLED = false;
|
||||
export const GLOBAL_STATE_CACHE_KEY = 'tt-global-state';
|
||||
|
||||
@ -10,12 +10,14 @@ import {
|
||||
MEDIA_PROGRESSIVE_CACHE_NAME,
|
||||
} from '../../../config';
|
||||
import { updateAppBadge } from '../../../util/appBadge';
|
||||
import { MAIN_IDB_STORE, PASSCODE_IDB_STORE } from '../../../util/browser/idb';
|
||||
import * as cacheApi from '../../../util/cacheApi';
|
||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||
import { buildCollectionByKey } from '../../../util/iteratees';
|
||||
import { unsubscribe } from '../../../util/notifications';
|
||||
import { clearEncryptedSession, encryptSession, forgetPasscode } from '../../../util/passcode';
|
||||
import { parseInitialLocationHash, resetInitialLocationHash, resetLocationHash } from '../../../util/routing';
|
||||
import { pause } from '../../../util/schedulers';
|
||||
import {
|
||||
clearStoredSession,
|
||||
loadStoredSession,
|
||||
@ -166,7 +168,7 @@ addActionHandler('signOut', async (global, actions, payload): Promise<void> => {
|
||||
resetInitialLocationHash();
|
||||
resetLocationHash();
|
||||
await unsubscribe();
|
||||
await callApi('destroy');
|
||||
await Promise.race([callApi('destroy'), pause(3000)]);
|
||||
await forceWebsync(false);
|
||||
} catch (err) {
|
||||
// Do nothing
|
||||
@ -194,6 +196,9 @@ addActionHandler('reset', (global, actions): ActionReturnType => {
|
||||
void cacheApi.clear(MEDIA_PROGRESSIVE_CACHE_NAME);
|
||||
void cacheApi.clear(CUSTOM_BG_CACHE_NAME);
|
||||
|
||||
MAIN_IDB_STORE.clear();
|
||||
PASSCODE_IDB_STORE.clear();
|
||||
|
||||
const langCachePrefix = LANG_CACHE_NAME.replace(/\d+$/, '');
|
||||
const langCacheVersion = Number((LANG_CACHE_NAME.match(/\d+$/) || ['0'])[0]);
|
||||
for (let i = 0; i < langCacheVersion; i++) {
|
||||
|
||||
@ -4,7 +4,7 @@ import { SettingsScreens } from '../../../types';
|
||||
import { getCurrentTabId, signalPasscodeHash } from '../../../util/establishMultitabRole';
|
||||
import { cloneDeep } from '../../../util/iteratees';
|
||||
import {
|
||||
clearEncryptedSession, decryptSession, encryptSession, forgetPasscode, setupPasscode,
|
||||
clearEncryptedSession, encryptSession, forgetPasscode, setupPasscode,
|
||||
} from '../../../util/passcode';
|
||||
import { onBeforeUnload } from '../../../util/schedulers';
|
||||
import { clearStoredSession, loadStoredSession, storeSession } from '../../../util/sessions';
|
||||
@ -100,13 +100,6 @@ addActionHandler('unlockScreen', (global, actions, payload): ActionReturnType =>
|
||||
actions.initApi();
|
||||
});
|
||||
|
||||
addActionHandler('decryptSession', (global, actions, payload): ActionReturnType => {
|
||||
const { passcode } = payload;
|
||||
decryptSession(passcode).then(actions.unlockScreen, () => {
|
||||
actions.logInvalidUnlockAttempt();
|
||||
});
|
||||
});
|
||||
|
||||
const MAX_INVALID_ATTEMPTS = 5;
|
||||
const TIMEOUT_RESET_INVALID_ATTEMPTS_MS = 1000 * 15;// 180000; // 3 minutes
|
||||
|
||||
|
||||
@ -3060,7 +3060,6 @@ export interface ActionPayloads {
|
||||
setPasscode: { passcode: string } & WithTabId;
|
||||
clearPasscode: undefined;
|
||||
lockScreen: undefined;
|
||||
decryptSession: { passcode: string };
|
||||
unlockScreen: { sessionJson: string; globalJson: string };
|
||||
softSignIn: undefined;
|
||||
logInvalidUnlockAttempt: undefined;
|
||||
|
||||
@ -66,3 +66,4 @@ class IdbStore {
|
||||
}
|
||||
|
||||
export const MAIN_IDB_STORE = new IdbStore('tt-data');
|
||||
export const PASSCODE_IDB_STORE = new IdbStore('tt-passcode');
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { PASSCODE_CACHE_NAME } from '../config';
|
||||
import { LEGACY_PASSCODE_CACHE_NAME } from '../config';
|
||||
import { PASSCODE_IDB_STORE } from './browser/idb';
|
||||
import * as cacheApi from './cacheApi';
|
||||
|
||||
const IV_LENGTH = 12;
|
||||
@ -6,6 +7,8 @@ const SALT = 'harder better faster stronger';
|
||||
|
||||
let currentPasscodeHash: ArrayBuffer | undefined;
|
||||
|
||||
export class UnrecoverablePasscodeError extends Error {}
|
||||
|
||||
export function getPasscodeHash() {
|
||||
return currentPasscodeHash;
|
||||
}
|
||||
@ -84,7 +87,7 @@ export async function decryptSession(passcode: string) {
|
||||
if (!sessionEncrypted || !globalEncrypted) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('[api/passcode] Missing required stored fields');
|
||||
throw new Error('[api/passcode] Missing required stored fields');
|
||||
throw new UnrecoverablePasscodeError('[api/passcode] Missing required stored fields');
|
||||
}
|
||||
|
||||
try {
|
||||
@ -109,24 +112,27 @@ export function forgetPasscode() {
|
||||
|
||||
export function clearEncryptedSession() {
|
||||
forgetPasscode();
|
||||
return cacheApi.clear(PASSCODE_CACHE_NAME);
|
||||
PASSCODE_IDB_STORE.clear();
|
||||
return cacheApi.clear(LEGACY_PASSCODE_CACHE_NAME);
|
||||
}
|
||||
|
||||
function sha256(plaintext: string) {
|
||||
return crypto.subtle.digest('SHA-256', new TextEncoder().encode(`${plaintext}${SALT}`));
|
||||
}
|
||||
|
||||
async function store(key: string, value: ArrayBuffer) {
|
||||
const isSuccessful = await cacheApi.save(PASSCODE_CACHE_NAME, key, value);
|
||||
if (isSuccessful) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error('Failed to save to cache');
|
||||
function store(key: string, value: ArrayBuffer) {
|
||||
const asArray = Array.from(new Uint8Array(value));
|
||||
PASSCODE_IDB_STORE.set(key, asArray);
|
||||
}
|
||||
|
||||
function load(key: string) {
|
||||
return cacheApi.fetch(PASSCODE_CACHE_NAME, key, cacheApi.Type.ArrayBuffer);
|
||||
async function load(key: string) {
|
||||
const cached = await PASSCODE_IDB_STORE.get<number[]>(key);
|
||||
if (cached) {
|
||||
const asArrayBuffer = new Uint8Array(cached).buffer;
|
||||
return asArrayBuffer;
|
||||
}
|
||||
// Fallback for old data
|
||||
return cacheApi.fetch(LEGACY_PASSCODE_CACHE_NAME, key, cacheApi.Type.ArrayBuffer);
|
||||
}
|
||||
|
||||
async function aesEncrypt(plaintext: string, pwHash: ArrayBuffer) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user