From 15e27968da29119f29e2ad7d38750e723199c140 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Sun, 16 Aug 2026 17:47:49 +0800 Subject: [PATCH] =?UTF-8?q?fix(rbac):=20user/up=5Flogin.dspy=20=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E5=AE=A1=E8=AE=A1=20+=20=E5=A4=B1=E8=B4=A55=E5=88=86?= =?UTF-8?q?=E9=92=9F=E7=AA=97=E5=8F=A3=E8=AE=A1=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- wwwroot/user/up_login.dspy | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/wwwroot/user/up_login.dspy b/wwwroot/user/up_login.dspy index 15f5252..9da3e80 100644 --- a/wwwroot/user/up_login.dspy +++ b/wwwroot/user/up_login.dspy @@ -4,6 +4,18 @@ ns = { "password":password_encode(params_kw.password) } +async def _audit_login(sor, user_id, username, action, result='ok'): + """登录审计:检查系统是否有审计模块,有则写入。""" + try: + from app_audit import audit_log + except ImportError: + return + try: + ip = request.get('client_ip', '') if hasattr(request, 'get') else '' + await audit_log(sor, user_id, username, action, result=result, client_ip=ip) + except Exception: + pass + info(f'{ns=}') db = DBPools() dbname = get_module_dbname('rbac') @@ -11,6 +23,7 @@ async with db.sqlorContext(dbname) as sor: # Get user record with lockout fields (standard SQL) r = await sor.sqlExe('select * from users where username=${username}$', ns.copy()) if len(r) == 0: + await _audit_login(sor, None, ns['username'], 'login_fail', 'fail') return { "status": "error", "data": { @@ -57,14 +70,16 @@ async with db.sqlorContext(dbname) as sor: # Verify password r = await sor.sqlExe('select * from users where username=${username}$ and password=${password}$', ns.copy()) if len(r) == 0: - # Atomic increment -- standard SQL + # 5分钟窗口:超过5分钟前的失败次数默认为0(原子 SQL) now_str = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + stale_before = (datetime.datetime.now() - datetime.timedelta(seconds=300)).strftime('%Y-%m-%d %H:%M:%S') await sor.sqlExe(""" UPDATE users - SET login_fail_count = login_fail_count + 1, + SET login_fail_count = CASE WHEN last_login_fail IS NULL OR last_login_fail < ${stale}$ THEN 1 ELSE login_fail_count + 1 END, last_login_fail = ${now}$ WHERE id = ${id}$ - """, {'id': user.id, 'now': now_str}) + """, {'id': user.id, 'now': now_str, 'stale': stale_before}) + await _audit_login(sor, user.id, ns['username'], 'login_fail', 'fail') new_fail_count = fail_count + 1 if new_fail_count >= 3: @@ -92,6 +107,7 @@ async with db.sqlorContext(dbname) as sor: WHERE id = ${id}$ """, {'id': user.id, 'now': now_str}) await remember_user(r[0].id, username=r[0].username, userorgid=r[0].orgid) + await _audit_login(sor, r[0].id, r[0].username, 'login', 'ok') return { "status": "ok", "data": { "user": r[0]},