implement account lockout after 3 failed login attempts with 5-minute cooldown period
This commit is contained in:
@@ -76,16 +76,16 @@ async function fetchApi<T>(
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => null);
|
||||
// Handle 401 unauthorized
|
||||
if (response.status === 401) {
|
||||
if (response.status === 401 && endpoint !== '/api/auth/login') {
|
||||
handleUnauthorized();
|
||||
throw new ApiError('Unauthorized', 401);
|
||||
}
|
||||
const errorData = await response.json().catch(() => null);
|
||||
throw new ApiError(
|
||||
errorData?.error || `HTTP ${response.status}`,
|
||||
response.status,
|
||||
errorData?.details
|
||||
errorData
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { create } from 'zustand';
|
||||
import { authApi } from '../api/auth';
|
||||
import { ApiError } from '../api/client';
|
||||
import { useToastStore } from './toastStore';
|
||||
import type { User, LoginRequest } from '../types';
|
||||
|
||||
@@ -16,6 +17,28 @@ interface AuthState {
|
||||
|
||||
const TOKEN_KEY = 'auth_token';
|
||||
const USER_KEY = 'auth_user';
|
||||
const LOCKED_ACCOUNT_CODE = 'account_temporarily_locked';
|
||||
|
||||
function getLoginErrorMessage(error: unknown): string {
|
||||
if (error instanceof ApiError) {
|
||||
const details = error.details as { code?: string; locked_until?: string } | null;
|
||||
|
||||
if (error.status === 429 && details?.code === LOCKED_ACCOUNT_CODE) {
|
||||
if (details.locked_until) {
|
||||
const lockedUntil = new Date(details.locked_until);
|
||||
const secondsLeft = Math.ceil((lockedUntil.getTime() - Date.now()) / 1000);
|
||||
if (Number.isFinite(secondsLeft) && secondsLeft > 0) {
|
||||
const minutesLeft = Math.ceil(secondsLeft / 60);
|
||||
return `Слишком много неверных попыток. Попробуйте через ${minutesLeft} мин.`;
|
||||
}
|
||||
}
|
||||
|
||||
return 'Слишком много неверных попыток. Попробуйте через 5 минут';
|
||||
}
|
||||
}
|
||||
|
||||
return error instanceof Error ? error.message : 'Ошибка входа';
|
||||
}
|
||||
|
||||
export const useAuthStore = create<AuthState>((set) => ({
|
||||
token: null,
|
||||
@@ -45,8 +68,7 @@ export const useAuthStore = create<AuthState>((set) => ({
|
||||
useToastStore.getState().addToast('Вход выполнен успешно', 'success');
|
||||
} catch (error) {
|
||||
set({ isLoading: false });
|
||||
const message = error instanceof Error ? error.message : 'Ошибка входа';
|
||||
useToastStore.getState().addToast(message, 'error');
|
||||
useToastStore.getState().addToast(getLoginErrorMessage(error), 'error');
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user