feat: Implement login brute-force protection with lockout mechanism

This commit is contained in:
wj-xiao
2026-02-28 16:44:47 +08:00
parent 11980b9122
commit afec4efb34
9 changed files with 242 additions and 62 deletions

View File

@@ -16,6 +16,8 @@ const en = {
noEmptyPassword: 'Password required',
noAccount: 'Failed to get user information, please refresh web page or reset password',
invalidUser: 'Invalid username or password',
locked: 'Too many logins, please try again later',
globalLocked: 'System under protection, please try again later',
error: 'Unexpected error',
changePassword: 'Change Password',
changePasswordDesc: 'For the security of your device, please change the password!',

View File

@@ -16,6 +16,8 @@ const zh = {
noEmptyPassword: '密码不能为空',
noAccount: '获取用户信息失败,请刷新重试或重置密码',
invalidUser: '用户名或密码错误',
locked: '登录太频繁,请稍后再试',
globalLocked: '系统防爆破保护中,请稍后再试',
error: '未知错误',
changePassword: '修改密码',
changePasswordDesc: '为了您的设备安全,请修改密码!',

View File

@@ -1,4 +1,4 @@
import { useEffect, useState, ReactElement } from 'react';
import { ReactElement, useEffect, useState } from 'react';
import { LockOutlined, UserOutlined } from '@ant-design/icons';
import { Button, Form, Input } from 'antd';
import { useTranslation } from 'react-i18next';
@@ -41,7 +41,12 @@ export const Login = (): ReactElement => {
.login(username, password)
.then((rsp: any) => {
if (rsp.code !== 0) {
setMsg(rsp.code === -2 ? t('auth.invalidUser') : t('auth.error'));
let errorMsg = t('auth.error');
if (rsp.code === -2) errorMsg = t('auth.invalidUser');
else if (rsp.code === -5) errorMsg = t('auth.locked');
else if (rsp.code === -4) errorMsg = t('auth.globalLocked');
setMsg(errorMsg);
return;
}
@@ -80,7 +85,8 @@ export const Login = (): ReactElement => {
setTimeout(() => {
(evt.target as HTMLImageElement).classList.remove('animate-spin');
}, 1000);
}} />
}}
/>
</div>
<Form.Item
name="username"
@@ -100,7 +106,7 @@ export const Login = (): ReactElement => {
/>
</Form.Item>
<div className="text-red-500">{msg}</div>
<div className="pb-1 text-red-500">{msg}</div>
<Form.Item>
<Button type="primary" htmlType="submit" className="w-full" loading={isLoading}>