diff --git a/src/components/main/LockScreen.tsx b/src/components/main/LockScreen.tsx index a91f89062..918bce67d 100644 --- a/src/components/main/LockScreen.tsx +++ b/src/components/main/LockScreen.tsx @@ -30,8 +30,6 @@ type StateProps = { passcodeSettings: GlobalState['passcode']; }; -const MAX_INVALID_ATTEMPTS = 5; -const TIMEOUT_RESET_INVALID_ATTEMPTS_MS = 180000; // 3 minutes const ICON_SIZE = 160; const LockScreen: FC = ({ @@ -47,6 +45,7 @@ const LockScreen: FC = ({ const { invalidAttemptsCount, + timeoutUntil, isLoading, } = passcodeSettings; @@ -56,19 +55,14 @@ const LockScreen: FC = ({ const [isSignOutDialogOpen, openSignOutConfirmation, closeSignOutConfirmation] = useFlag(false); const { shouldRender } = useShowTransition(isLocked); - useTimeout( - resetInvalidUnlockAttempts, - invalidAttemptsCount && invalidAttemptsCount >= MAX_INVALID_ATTEMPTS - ? TIMEOUT_RESET_INVALID_ATTEMPTS_MS - : undefined, - ); + useTimeout(resetInvalidUnlockAttempts, timeoutUntil ? timeoutUntil - Date.now() : undefined); const handleClearError = useCallback(() => { setValidationError(''); }, []); const handleSubmit = useCallback((passcode: string) => { - if (invalidAttemptsCount && invalidAttemptsCount >= MAX_INVALID_ATTEMPTS) { + if (timeoutUntil !== undefined) { setValidationError(lang('FloodWait')); return; } @@ -78,15 +72,15 @@ const LockScreen: FC = ({ logInvalidUnlockAttempt(); setValidationError(lang('lng_passcode_wrong')); }); - }, [invalidAttemptsCount, lang, logInvalidUnlockAttempt, unlockScreen]); + }, [lang, timeoutUntil]); useEffect(() => { - if (invalidAttemptsCount && invalidAttemptsCount >= MAX_INVALID_ATTEMPTS) { + if (timeoutUntil !== undefined) { setValidationError(lang('FloodWait')); } else if (invalidAttemptsCount === 0) { setValidationError(''); } - }, [invalidAttemptsCount, lang]); + }, [timeoutUntil, lang, invalidAttemptsCount]); const handleSignOutMessage = useCallback(() => { closeSignOutConfirmation(); diff --git a/src/global/actions/api/initial.ts b/src/global/actions/api/initial.ts index a57a87a2f..4f1a56954 100644 --- a/src/global/actions/api/initial.ts +++ b/src/global/actions/api/initial.ts @@ -252,6 +252,7 @@ addActionHandler('lockScreen', async (global): Promise => { { isScreenLocked: true, invalidAttemptsCount: 0, + timeoutUntil: undefined, }, ); setGlobal(global); diff --git a/src/global/actions/ui/passcode.ts b/src/global/actions/ui/passcode.ts index 2811eae09..0b6579dc1 100644 --- a/src/global/actions/ui/passcode.ts +++ b/src/global/actions/ui/passcode.ts @@ -90,15 +90,23 @@ addActionHandler('decryptSession', (global, actions, payload): ActionReturnType }); }); +const MAX_INVALID_ATTEMPTS = 5; +const TIMEOUT_RESET_INVALID_ATTEMPTS_MS = 1000 * 15;// 180000; // 3 minutes + addActionHandler('logInvalidUnlockAttempt', (global): ActionReturnType => { + const invalidAttemptsCount = (global.passcode?.invalidAttemptsCount ?? 0) + 1; + return updatePasscodeSettings(global, { - invalidAttemptsCount: (global.passcode?.invalidAttemptsCount ?? 0) + 1, + invalidAttemptsCount, + timeoutUntil: (invalidAttemptsCount >= MAX_INVALID_ATTEMPTS + ? Date.now() + TIMEOUT_RESET_INVALID_ATTEMPTS_MS : undefined), }); }); addActionHandler('resetInvalidUnlockAttempts', (global): ActionReturnType => { return updatePasscodeSettings(global, { invalidAttemptsCount: 0, + timeoutUntil: undefined, }); }); diff --git a/src/global/cache.ts b/src/global/cache.ts index 1c8d7f502..699c20f5d 100644 --- a/src/global/cache.ts +++ b/src/global/cache.ts @@ -357,6 +357,7 @@ export function serializeGlobal(global: T) { 'isScreenLocked', 'hasPasscode', 'invalidAttemptsCount', + 'timeoutUntil', ]), }; diff --git a/src/global/types.ts b/src/global/types.ts index f6f7e57db..3e066a532 100644 --- a/src/global/types.ts +++ b/src/global/types.ts @@ -592,6 +592,7 @@ export type GlobalState = { isScreenLocked?: boolean; hasPasscode?: boolean; error?: string; + timeoutUntil?: number; invalidAttemptsCount?: number; invalidAttemptError?: string; isLoading?: boolean; diff --git a/src/util/passcode.ts b/src/util/passcode.ts index 147dc8655..063f21008 100644 --- a/src/util/passcode.ts +++ b/src/util/passcode.ts @@ -20,6 +20,8 @@ export async function setupPasscode(passcode: string) { export async function encryptSession(sessionJson?: string, globalJson?: string) { if (!currentPasscodeHash) { + // eslint-disable-next-line no-console + console.error('[api/passcode] Missing current passcode'); throw new Error('[api/passcode] Missing current passcode'); } @@ -41,6 +43,8 @@ export async function encryptSession(sessionJson?: string, globalJson?: string) export async function decryptSessionByCurrentHash() { if (!currentPasscodeHash) { + // eslint-disable-next-line no-console + console.error('[api/passcode] Missing current passcode'); throw new Error('[api/passcode] Missing current passcode'); } @@ -50,6 +54,8 @@ export async function decryptSessionByCurrentHash() { ]); 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'); } @@ -70,6 +76,8 @@ 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'); }