Passcode: Save last passcode timeout (#2876)
This commit is contained in:
parent
7845ed4e1e
commit
e3dd98882b
@ -30,8 +30,6 @@ type StateProps = {
|
|||||||
passcodeSettings: GlobalState['passcode'];
|
passcodeSettings: GlobalState['passcode'];
|
||||||
};
|
};
|
||||||
|
|
||||||
const MAX_INVALID_ATTEMPTS = 5;
|
|
||||||
const TIMEOUT_RESET_INVALID_ATTEMPTS_MS = 180000; // 3 minutes
|
|
||||||
const ICON_SIZE = 160;
|
const ICON_SIZE = 160;
|
||||||
|
|
||||||
const LockScreen: FC<OwnProps & StateProps> = ({
|
const LockScreen: FC<OwnProps & StateProps> = ({
|
||||||
@ -47,6 +45,7 @@ const LockScreen: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
invalidAttemptsCount,
|
invalidAttemptsCount,
|
||||||
|
timeoutUntil,
|
||||||
isLoading,
|
isLoading,
|
||||||
} = passcodeSettings;
|
} = passcodeSettings;
|
||||||
|
|
||||||
@ -56,19 +55,14 @@ const LockScreen: FC<OwnProps & StateProps> = ({
|
|||||||
const [isSignOutDialogOpen, openSignOutConfirmation, closeSignOutConfirmation] = useFlag(false);
|
const [isSignOutDialogOpen, openSignOutConfirmation, closeSignOutConfirmation] = useFlag(false);
|
||||||
const { shouldRender } = useShowTransition(isLocked);
|
const { shouldRender } = useShowTransition(isLocked);
|
||||||
|
|
||||||
useTimeout(
|
useTimeout(resetInvalidUnlockAttempts, timeoutUntil ? timeoutUntil - Date.now() : undefined);
|
||||||
resetInvalidUnlockAttempts,
|
|
||||||
invalidAttemptsCount && invalidAttemptsCount >= MAX_INVALID_ATTEMPTS
|
|
||||||
? TIMEOUT_RESET_INVALID_ATTEMPTS_MS
|
|
||||||
: undefined,
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleClearError = useCallback(() => {
|
const handleClearError = useCallback(() => {
|
||||||
setValidationError('');
|
setValidationError('');
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleSubmit = useCallback((passcode: string) => {
|
const handleSubmit = useCallback((passcode: string) => {
|
||||||
if (invalidAttemptsCount && invalidAttemptsCount >= MAX_INVALID_ATTEMPTS) {
|
if (timeoutUntil !== undefined) {
|
||||||
setValidationError(lang('FloodWait'));
|
setValidationError(lang('FloodWait'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -78,15 +72,15 @@ const LockScreen: FC<OwnProps & StateProps> = ({
|
|||||||
logInvalidUnlockAttempt();
|
logInvalidUnlockAttempt();
|
||||||
setValidationError(lang('lng_passcode_wrong'));
|
setValidationError(lang('lng_passcode_wrong'));
|
||||||
});
|
});
|
||||||
}, [invalidAttemptsCount, lang, logInvalidUnlockAttempt, unlockScreen]);
|
}, [lang, timeoutUntil]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (invalidAttemptsCount && invalidAttemptsCount >= MAX_INVALID_ATTEMPTS) {
|
if (timeoutUntil !== undefined) {
|
||||||
setValidationError(lang('FloodWait'));
|
setValidationError(lang('FloodWait'));
|
||||||
} else if (invalidAttemptsCount === 0) {
|
} else if (invalidAttemptsCount === 0) {
|
||||||
setValidationError('');
|
setValidationError('');
|
||||||
}
|
}
|
||||||
}, [invalidAttemptsCount, lang]);
|
}, [timeoutUntil, lang, invalidAttemptsCount]);
|
||||||
|
|
||||||
const handleSignOutMessage = useCallback(() => {
|
const handleSignOutMessage = useCallback(() => {
|
||||||
closeSignOutConfirmation();
|
closeSignOutConfirmation();
|
||||||
|
|||||||
@ -252,6 +252,7 @@ addActionHandler('lockScreen', async (global): Promise<void> => {
|
|||||||
{
|
{
|
||||||
isScreenLocked: true,
|
isScreenLocked: true,
|
||||||
invalidAttemptsCount: 0,
|
invalidAttemptsCount: 0,
|
||||||
|
timeoutUntil: undefined,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|||||||
@ -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 => {
|
addActionHandler('logInvalidUnlockAttempt', (global): ActionReturnType => {
|
||||||
|
const invalidAttemptsCount = (global.passcode?.invalidAttemptsCount ?? 0) + 1;
|
||||||
|
|
||||||
return updatePasscodeSettings(global, {
|
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 => {
|
addActionHandler('resetInvalidUnlockAttempts', (global): ActionReturnType => {
|
||||||
return updatePasscodeSettings(global, {
|
return updatePasscodeSettings(global, {
|
||||||
invalidAttemptsCount: 0,
|
invalidAttemptsCount: 0,
|
||||||
|
timeoutUntil: undefined,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -357,6 +357,7 @@ export function serializeGlobal<T extends GlobalState>(global: T) {
|
|||||||
'isScreenLocked',
|
'isScreenLocked',
|
||||||
'hasPasscode',
|
'hasPasscode',
|
||||||
'invalidAttemptsCount',
|
'invalidAttemptsCount',
|
||||||
|
'timeoutUntil',
|
||||||
]),
|
]),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -592,6 +592,7 @@ export type GlobalState = {
|
|||||||
isScreenLocked?: boolean;
|
isScreenLocked?: boolean;
|
||||||
hasPasscode?: boolean;
|
hasPasscode?: boolean;
|
||||||
error?: string;
|
error?: string;
|
||||||
|
timeoutUntil?: number;
|
||||||
invalidAttemptsCount?: number;
|
invalidAttemptsCount?: number;
|
||||||
invalidAttemptError?: string;
|
invalidAttemptError?: string;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
|
|||||||
@ -20,6 +20,8 @@ export async function setupPasscode(passcode: string) {
|
|||||||
|
|
||||||
export async function encryptSession(sessionJson?: string, globalJson?: string) {
|
export async function encryptSession(sessionJson?: string, globalJson?: string) {
|
||||||
if (!currentPasscodeHash) {
|
if (!currentPasscodeHash) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error('[api/passcode] Missing current passcode');
|
||||||
throw new 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() {
|
export async function decryptSessionByCurrentHash() {
|
||||||
if (!currentPasscodeHash) {
|
if (!currentPasscodeHash) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error('[api/passcode] Missing current passcode');
|
||||||
throw new 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) {
|
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 Error('[api/passcode] Missing required stored fields');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,6 +76,8 @@ export async function decryptSession(passcode: string) {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
if (!sessionEncrypted || !globalEncrypted) {
|
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 Error('[api/passcode] Missing required stored fields');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user