mdbq 4.0.90__py3-none-any.whl → 4.0.92__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mdbq/__version__.py +1 -1
- mdbq/auth/auth_backend.py +276 -34
- mdbq/auth/rate_limiter.py +1 -1
- {mdbq-4.0.90.dist-info → mdbq-4.0.92.dist-info}/METADATA +1 -1
- {mdbq-4.0.90.dist-info → mdbq-4.0.92.dist-info}/RECORD +7 -7
- {mdbq-4.0.90.dist-info → mdbq-4.0.92.dist-info}/WHEEL +0 -0
- {mdbq-4.0.90.dist-info → mdbq-4.0.92.dist-info}/top_level.txt +0 -0
mdbq/__version__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
VERSION = '4.0.
|
1
|
+
VERSION = '4.0.92'
|
mdbq/auth/auth_backend.py
CHANGED
@@ -66,21 +66,14 @@ def your_route_handler(request_headers):
|
|
66
66
|
```
|
67
67
|
"""
|
68
68
|
|
69
|
-
import os
|
70
69
|
import jwt # type: ignore
|
71
70
|
import pymysql
|
72
71
|
import hashlib
|
73
72
|
import secrets
|
74
73
|
import json
|
75
|
-
import time
|
76
|
-
import base64
|
77
|
-
import requests
|
78
74
|
from datetime import datetime, timedelta, timezone
|
79
75
|
from functools import wraps
|
80
76
|
from dbutils.pooled_db import PooledDB # type: ignore
|
81
|
-
from cryptography.hazmat.primitives.asymmetric import padding
|
82
|
-
from cryptography.hazmat.primitives import hashes, serialization
|
83
|
-
from cryptography.hazmat.backends import default_backend
|
84
77
|
|
85
78
|
# Flask相关导入 - 用于装饰器功能
|
86
79
|
try:
|
@@ -196,7 +189,9 @@ class StandaloneAuthManager:
|
|
196
189
|
CREATE TABLE IF NOT EXISTS users (
|
197
190
|
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
198
191
|
username VARCHAR(50) NOT NULL,
|
192
|
+
email VARCHAR(100) NOT NULL,
|
199
193
|
password_hash VARCHAR(128) NOT NULL,
|
194
|
+
password_plain TEXT NOT NULL,
|
200
195
|
salt VARCHAR(64) NOT NULL,
|
201
196
|
role ENUM('admin', 'user', 'manager') NOT NULL DEFAULT 'user',
|
202
197
|
permissions JSON DEFAULT (JSON_ARRAY()),
|
@@ -205,12 +200,17 @@ class StandaloneAuthManager:
|
|
205
200
|
last_login TIMESTAMP(3) NULL DEFAULT NULL,
|
206
201
|
login_attempts TINYINT UNSIGNED NOT NULL DEFAULT 0,
|
207
202
|
locked_until TIMESTAMP(3) NULL DEFAULT NULL,
|
203
|
+
password_reset_token VARCHAR(64) NULL DEFAULT NULL COMMENT '密码重置令牌',
|
204
|
+
password_reset_expires TIMESTAMP(3) NULL DEFAULT NULL COMMENT '重置令牌过期时间',
|
208
205
|
|
209
206
|
UNIQUE KEY uk_users_username (username),
|
207
|
+
UNIQUE KEY uk_users_email (email),
|
208
|
+
UNIQUE KEY uk_users_reset_token (password_reset_token),
|
210
209
|
KEY idx_users_role (role),
|
211
210
|
KEY idx_users_created_at (created_at),
|
212
211
|
KEY idx_users_is_active (is_active),
|
213
|
-
KEY idx_users_locked_until (locked_until)
|
212
|
+
KEY idx_users_locked_until (locked_until),
|
213
|
+
KEY idx_users_reset_expires (password_reset_expires)
|
214
214
|
) ENGINE=InnoDB
|
215
215
|
DEFAULT CHARSET=utf8mb4
|
216
216
|
COLLATE=utf8mb4_0900_ai_ci
|
@@ -374,15 +374,15 @@ class StandaloneAuthManager:
|
|
374
374
|
cursor.close()
|
375
375
|
conn.close()
|
376
376
|
|
377
|
-
def register_user(self, username, password, role='user', permissions=None):
|
377
|
+
def register_user(self, username, password, role='user', permissions=None, email=None):
|
378
378
|
"""用户注册"""
|
379
379
|
conn = self.pool.connection()
|
380
380
|
cursor = conn.cursor()
|
381
381
|
|
382
382
|
try:
|
383
383
|
# 验证输入
|
384
|
-
if not username or not password:
|
385
|
-
return {'success': False, 'message': '
|
384
|
+
if not username or not password or not email:
|
385
|
+
return {'success': False, 'message': '用户名、密码和邮箱不能为空'}
|
386
386
|
|
387
387
|
if len(username.strip()) < 3:
|
388
388
|
return {'success': False, 'message': '用户名至少需要3个字符'}
|
@@ -390,13 +390,25 @@ class StandaloneAuthManager:
|
|
390
390
|
if len(password) < 6:
|
391
391
|
return {'success': False, 'message': '密码至少需要6个字符'}
|
392
392
|
|
393
|
+
# 邮箱格式验证
|
394
|
+
import re
|
395
|
+
email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
|
396
|
+
if not re.match(email_pattern, email):
|
397
|
+
return {'success': False, 'message': '请输入有效的邮箱地址'}
|
398
|
+
|
393
399
|
username = username.strip()
|
400
|
+
email = email.strip().lower()
|
394
401
|
|
395
402
|
# 检查用户名是否已存在
|
396
403
|
cursor.execute('SELECT id FROM users WHERE username = %s', (username,))
|
397
404
|
if cursor.fetchone():
|
398
405
|
return {'success': False, 'message': '用户名已被占用'}
|
399
406
|
|
407
|
+
# 检查邮箱是否已存在
|
408
|
+
cursor.execute('SELECT id FROM users WHERE email = %s', (email,))
|
409
|
+
if cursor.fetchone():
|
410
|
+
return {'success': False, 'message': '邮箱已被注册'}
|
411
|
+
|
400
412
|
# 生成盐值和密码哈希
|
401
413
|
salt = secrets.token_hex(32)
|
402
414
|
password_hash = self._hash_password(password, salt)
|
@@ -408,9 +420,9 @@ class StandaloneAuthManager:
|
|
408
420
|
|
409
421
|
# 创建新用户
|
410
422
|
cursor.execute('''
|
411
|
-
INSERT INTO users (username, password_hash, salt, role, permissions, is_active)
|
412
|
-
VALUES (%s, %s, %s, %s, %s, %s)
|
413
|
-
''', (username, password_hash, salt, role, permissions_json, True))
|
423
|
+
INSERT INTO users (username, email, password_hash, password_plain, salt, role, permissions, is_active)
|
424
|
+
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
|
425
|
+
''', (username, email, password_hash, password, salt, role, permissions_json, True))
|
414
426
|
|
415
427
|
user_id = cursor.lastrowid
|
416
428
|
|
@@ -420,6 +432,7 @@ class StandaloneAuthManager:
|
|
420
432
|
'user': {
|
421
433
|
'id': user_id,
|
422
434
|
'username': username,
|
435
|
+
'email': email,
|
423
436
|
'role': role,
|
424
437
|
'permissions': permissions
|
425
438
|
}
|
@@ -431,13 +444,13 @@ class StandaloneAuthManager:
|
|
431
444
|
cursor.close()
|
432
445
|
conn.close()
|
433
446
|
|
434
|
-
def authenticate_user(self,
|
435
|
-
"""用户身份验证"""
|
447
|
+
def authenticate_user(self, username_or_email, password, ip_address=None, user_agent=None):
|
448
|
+
"""用户身份验证 - 支持用户名或邮箱登录"""
|
436
449
|
|
437
450
|
# 检查IP是否被限流
|
438
451
|
ip_check = self._check_ip_rate_limit(ip_address, 'login')
|
439
452
|
if ip_check['blocked']:
|
440
|
-
self._log_login_attempt(
|
453
|
+
self._log_login_attempt(username_or_email, ip_address, user_agent, 'failure', f'ip_blocked_{ip_check["remaining_time"]}s')
|
441
454
|
return {
|
442
455
|
'success': False,
|
443
456
|
'error': 'ip_blocked',
|
@@ -449,24 +462,26 @@ class StandaloneAuthManager:
|
|
449
462
|
cursor = conn.cursor()
|
450
463
|
|
451
464
|
try:
|
452
|
-
# 获取用户信息
|
465
|
+
# 获取用户信息 - 支持用户名或邮箱
|
453
466
|
cursor.execute('''
|
454
|
-
SELECT id, username, password_hash, salt, role, permissions,
|
467
|
+
SELECT id, username, email, password_hash, salt, role, permissions,
|
455
468
|
is_active, login_attempts, locked_until
|
456
|
-
FROM users WHERE username = %s
|
457
|
-
''', (
|
469
|
+
FROM users WHERE username = %s OR email = %s
|
470
|
+
''', (username_or_email, username_or_email))
|
458
471
|
|
459
472
|
user = cursor.fetchone()
|
460
473
|
if not user:
|
461
|
-
self._log_login_attempt(
|
474
|
+
self._log_login_attempt(username_or_email, ip_address, user_agent, 'failure', 'user_not_found')
|
462
475
|
self._record_ip_failure(ip_address, 'login')
|
463
476
|
return {
|
464
477
|
'success': False,
|
465
478
|
'error': 'invalid_credentials',
|
466
|
-
'message': '
|
479
|
+
'message': '用户名/邮箱或密码错误'
|
467
480
|
}
|
468
481
|
|
469
482
|
user_id = user['id']
|
483
|
+
username = user['username']
|
484
|
+
email = user['email']
|
470
485
|
password_hash = user['password_hash']
|
471
486
|
salt = user['salt']
|
472
487
|
role = user['role']
|
@@ -477,7 +492,7 @@ class StandaloneAuthManager:
|
|
477
492
|
|
478
493
|
# 检查账户状态
|
479
494
|
if not is_active:
|
480
|
-
self._log_login_attempt(
|
495
|
+
self._log_login_attempt(username_or_email, ip_address, user_agent, 'failure', 'account_disabled', user_id)
|
481
496
|
self._record_ip_failure(ip_address, 'login')
|
482
497
|
return {
|
483
498
|
'success': False,
|
@@ -495,7 +510,7 @@ class StandaloneAuthManager:
|
|
495
510
|
|
496
511
|
if locked_until > current_time_utc:
|
497
512
|
remaining_seconds = int((locked_until - current_time_utc).total_seconds())
|
498
|
-
self._log_login_attempt(
|
513
|
+
self._log_login_attempt(username_or_email, ip_address, user_agent, 'failure', 'account_locked', user_id)
|
499
514
|
self._record_ip_failure(ip_address, 'login')
|
500
515
|
return {
|
501
516
|
'success': False,
|
@@ -516,7 +531,7 @@ class StandaloneAuthManager:
|
|
516
531
|
UPDATE users SET login_attempts = %s, locked_until = %s WHERE id = %s
|
517
532
|
''', (login_attempts, locked_until, user_id))
|
518
533
|
|
519
|
-
self._log_login_attempt(
|
534
|
+
self._log_login_attempt(username_or_email, ip_address, user_agent, 'failure', f'password_incorrect_locked_{lockout_duration}s', user_id)
|
520
535
|
self._record_ip_failure(ip_address, 'login')
|
521
536
|
|
522
537
|
return {
|
@@ -530,14 +545,14 @@ class StandaloneAuthManager:
|
|
530
545
|
UPDATE users SET login_attempts = %s WHERE id = %s
|
531
546
|
''', (login_attempts, user_id))
|
532
547
|
|
533
|
-
self._log_login_attempt(
|
548
|
+
self._log_login_attempt(username_or_email, ip_address, user_agent, 'failure', f'password_incorrect_attempt_{login_attempts}', user_id)
|
534
549
|
self._record_ip_failure(ip_address, 'login')
|
535
550
|
|
536
551
|
remaining_attempts = self.auth_config['max_login_attempts'] - login_attempts
|
537
552
|
return {
|
538
553
|
'success': False,
|
539
554
|
'error': 'invalid_credentials',
|
540
|
-
'message': f'
|
555
|
+
'message': f'用户名/邮箱或密码错误,还可以尝试 {remaining_attempts} 次',
|
541
556
|
'remaining_attempts': remaining_attempts
|
542
557
|
}
|
543
558
|
|
@@ -547,13 +562,14 @@ class StandaloneAuthManager:
|
|
547
562
|
''', (current_time_utc, user_id))
|
548
563
|
|
549
564
|
# 记录成功登录
|
550
|
-
self._log_login_attempt(
|
565
|
+
self._log_login_attempt(username_or_email, ip_address, user_agent, 'success', None, user_id)
|
551
566
|
self._reset_ip_failures(ip_address, 'login')
|
552
567
|
|
553
568
|
return {
|
554
569
|
'success': True,
|
555
570
|
'user_id': user_id,
|
556
|
-
'username':
|
571
|
+
'username': username,
|
572
|
+
'email': email,
|
557
573
|
'role': role,
|
558
574
|
'permissions': self._safe_json_parse(permissions),
|
559
575
|
'last_login': current_time_utc.isoformat()
|
@@ -830,6 +846,201 @@ class StandaloneAuthManager:
|
|
830
846
|
cursor.close()
|
831
847
|
conn.close()
|
832
848
|
|
849
|
+
def change_password(self, user_id, old_password, new_password):
|
850
|
+
"""修改密码"""
|
851
|
+
conn = self.pool.connection()
|
852
|
+
cursor = conn.cursor()
|
853
|
+
|
854
|
+
try:
|
855
|
+
# 验证输入
|
856
|
+
if not old_password or not new_password:
|
857
|
+
return {'success': False, 'message': '旧密码和新密码不能为空'}
|
858
|
+
|
859
|
+
if len(new_password) < 6:
|
860
|
+
return {'success': False, 'message': '新密码至少需要6个字符'}
|
861
|
+
|
862
|
+
# 获取用户当前密码信息
|
863
|
+
cursor.execute('''
|
864
|
+
SELECT username, password_hash, salt, is_active
|
865
|
+
FROM users WHERE id = %s
|
866
|
+
''', (user_id,))
|
867
|
+
|
868
|
+
user = cursor.fetchone()
|
869
|
+
if not user:
|
870
|
+
return {'success': False, 'message': '用户不存在'}
|
871
|
+
|
872
|
+
if not user['is_active']:
|
873
|
+
return {'success': False, 'message': '账户已被禁用'}
|
874
|
+
|
875
|
+
# 验证旧密码
|
876
|
+
if not self._verify_password(old_password, user['password_hash'], user['salt']):
|
877
|
+
return {'success': False, 'message': '旧密码错误'}
|
878
|
+
|
879
|
+
# 生成新的盐值和密码哈希
|
880
|
+
new_salt = secrets.token_hex(32)
|
881
|
+
new_password_hash = self._hash_password(new_password, new_salt)
|
882
|
+
|
883
|
+
current_time_utc = datetime.now(timezone.utc)
|
884
|
+
|
885
|
+
# 更新密码
|
886
|
+
cursor.execute('''
|
887
|
+
UPDATE users
|
888
|
+
SET password_hash = %s, password_plain = %s, salt = %s,
|
889
|
+
login_attempts = 0, locked_until = NULL
|
890
|
+
WHERE id = %s
|
891
|
+
''', (new_password_hash, new_password, new_salt, user_id))
|
892
|
+
|
893
|
+
# 撤销所有刷新令牌(强制重新登录)
|
894
|
+
cursor.execute('''
|
895
|
+
UPDATE refresh_tokens
|
896
|
+
SET is_revoked = 1, revoked_at = %s, revoked_reason = 'password_changed'
|
897
|
+
WHERE user_id = %s AND is_revoked = 0
|
898
|
+
''', (current_time_utc, user_id))
|
899
|
+
|
900
|
+
# 停用所有设备会话
|
901
|
+
cursor.execute('''
|
902
|
+
UPDATE device_sessions
|
903
|
+
SET is_active = 0
|
904
|
+
WHERE user_id = %s AND is_active = 1
|
905
|
+
''', (user_id,))
|
906
|
+
|
907
|
+
return {
|
908
|
+
'success': True,
|
909
|
+
'message': '密码修改成功,请重新登录'
|
910
|
+
}
|
911
|
+
|
912
|
+
except Exception as e:
|
913
|
+
return {'success': False, 'message': f'密码修改失败: {str(e)}'}
|
914
|
+
finally:
|
915
|
+
cursor.close()
|
916
|
+
conn.close()
|
917
|
+
|
918
|
+
def request_password_reset(self, username, email):
|
919
|
+
"""请求密码重置 - 需要同时验证用户名和邮箱"""
|
920
|
+
conn = self.pool.connection()
|
921
|
+
cursor = conn.cursor()
|
922
|
+
|
923
|
+
try:
|
924
|
+
# 查找用户 - 必须用户名和邮箱都匹配
|
925
|
+
cursor.execute('''
|
926
|
+
SELECT id, username, email, is_active
|
927
|
+
FROM users WHERE username = %s AND email = %s
|
928
|
+
''', (username, email))
|
929
|
+
|
930
|
+
user = cursor.fetchone()
|
931
|
+
if not user:
|
932
|
+
# 为安全起见,统一返回错误信息
|
933
|
+
return {
|
934
|
+
'success': False,
|
935
|
+
'message': '用户名或邮箱不正确'
|
936
|
+
}
|
937
|
+
|
938
|
+
if not user['is_active']:
|
939
|
+
# 账户被禁用,也返回用户名或邮箱不正确
|
940
|
+
return {
|
941
|
+
'success': False,
|
942
|
+
'message': '用户名或邮箱不正确'
|
943
|
+
}
|
944
|
+
|
945
|
+
# 生成重置令牌
|
946
|
+
reset_token = secrets.token_urlsafe(32)
|
947
|
+
expires_at = datetime.now(timezone.utc) + timedelta(hours=1) # 1小时有效期
|
948
|
+
|
949
|
+
# 保存重置令牌
|
950
|
+
cursor.execute('''
|
951
|
+
UPDATE users
|
952
|
+
SET password_reset_token = %s, password_reset_expires = %s
|
953
|
+
WHERE id = %s
|
954
|
+
''', (reset_token, expires_at, user['id']))
|
955
|
+
|
956
|
+
# 邮箱脱敏处理
|
957
|
+
masked_email = self._mask_email(user['email'])
|
958
|
+
|
959
|
+
# 直接返回重置令牌给前端
|
960
|
+
return {
|
961
|
+
'success': True,
|
962
|
+
'message': f'验证成功,重置令牌已生成(邮箱:{masked_email})',
|
963
|
+
'data': {
|
964
|
+
'reset_token': reset_token,
|
965
|
+
'masked_email': masked_email,
|
966
|
+
'username': user['username'],
|
967
|
+
'expires_at': expires_at.isoformat()
|
968
|
+
}
|
969
|
+
}
|
970
|
+
|
971
|
+
except Exception as e:
|
972
|
+
return {'success': False, 'message': f'密码重置请求失败: {str(e)}'}
|
973
|
+
finally:
|
974
|
+
cursor.close()
|
975
|
+
conn.close()
|
976
|
+
|
977
|
+
def reset_password_with_token(self, reset_token, new_password):
|
978
|
+
"""使用重置令牌重置密码"""
|
979
|
+
conn = self.pool.connection()
|
980
|
+
cursor = conn.cursor()
|
981
|
+
|
982
|
+
try:
|
983
|
+
# 验证输入
|
984
|
+
if not reset_token or not new_password:
|
985
|
+
return {'success': False, 'message': '重置令牌和新密码不能为空'}
|
986
|
+
|
987
|
+
if len(new_password) < 6:
|
988
|
+
return {'success': False, 'message': '新密码至少需要6个字符'}
|
989
|
+
|
990
|
+
current_time_utc = datetime.now(timezone.utc)
|
991
|
+
|
992
|
+
# 查找有效的重置令牌
|
993
|
+
cursor.execute('''
|
994
|
+
SELECT id, username, is_active, password_reset_expires
|
995
|
+
FROM users
|
996
|
+
WHERE password_reset_token = %s
|
997
|
+
AND password_reset_expires > %s
|
998
|
+
AND is_active = 1
|
999
|
+
''', (reset_token, current_time_utc))
|
1000
|
+
|
1001
|
+
user = cursor.fetchone()
|
1002
|
+
if not user:
|
1003
|
+
return {'success': False, 'message': '重置令牌无效或已过期'}
|
1004
|
+
|
1005
|
+
# 生成新的盐值和密码哈希
|
1006
|
+
new_salt = secrets.token_hex(32)
|
1007
|
+
new_password_hash = self._hash_password(new_password, new_salt)
|
1008
|
+
|
1009
|
+
# 更新密码并清除重置令牌
|
1010
|
+
cursor.execute('''
|
1011
|
+
UPDATE users
|
1012
|
+
SET password_hash = %s, password_plain = %s, salt = %s,
|
1013
|
+
password_reset_token = NULL, password_reset_expires = NULL,
|
1014
|
+
login_attempts = 0, locked_until = NULL
|
1015
|
+
WHERE id = %s
|
1016
|
+
''', (new_password_hash, new_password, new_salt, user['id']))
|
1017
|
+
|
1018
|
+
# 撤销所有刷新令牌
|
1019
|
+
cursor.execute('''
|
1020
|
+
UPDATE refresh_tokens
|
1021
|
+
SET is_revoked = 1, revoked_at = %s, revoked_reason = 'password_reset'
|
1022
|
+
WHERE user_id = %s AND is_revoked = 0
|
1023
|
+
''', (current_time_utc, user['id']))
|
1024
|
+
|
1025
|
+
# 停用所有设备会话
|
1026
|
+
cursor.execute('''
|
1027
|
+
UPDATE device_sessions
|
1028
|
+
SET is_active = 0
|
1029
|
+
WHERE user_id = %s AND is_active = 1
|
1030
|
+
''', (user['id'],))
|
1031
|
+
|
1032
|
+
return {
|
1033
|
+
'success': True,
|
1034
|
+
'message': '密码重置成功,请使用新密码登录',
|
1035
|
+
'username': user['username']
|
1036
|
+
}
|
1037
|
+
|
1038
|
+
except Exception as e:
|
1039
|
+
return {'success': False, 'message': f'密码重置失败: {str(e)}'}
|
1040
|
+
finally:
|
1041
|
+
cursor.close()
|
1042
|
+
conn.close()
|
1043
|
+
|
833
1044
|
# ==================== 辅助方法 ====================
|
834
1045
|
|
835
1046
|
def _check_ip_rate_limit(self, ip_address, action_type='login'):
|
@@ -1034,6 +1245,33 @@ class StandaloneAuthManager:
|
|
1034
1245
|
return []
|
1035
1246
|
return json_str or []
|
1036
1247
|
|
1248
|
+
def _mask_email(self, email):
|
1249
|
+
"""邮箱脱敏处理"""
|
1250
|
+
if not email or '@' not in email:
|
1251
|
+
return email
|
1252
|
+
|
1253
|
+
local_part, domain_part = email.split('@', 1)
|
1254
|
+
|
1255
|
+
# 处理本地部分(@前面的部分)
|
1256
|
+
if len(local_part) <= 3:
|
1257
|
+
# 短邮箱名,只显示第一个字符
|
1258
|
+
masked_local = local_part[0] + '***'
|
1259
|
+
else:
|
1260
|
+
# 长邮箱名,显示前3个字符
|
1261
|
+
masked_local = local_part[:3] + '***'
|
1262
|
+
|
1263
|
+
# 处理域名部分
|
1264
|
+
if '.' in domain_part:
|
1265
|
+
domain_name, domain_ext = domain_part.rsplit('.', 1)
|
1266
|
+
if len(domain_name) <= 2:
|
1267
|
+
masked_domain = '***.' + domain_ext
|
1268
|
+
else:
|
1269
|
+
masked_domain = domain_name[:2] + '***.' + domain_ext
|
1270
|
+
else:
|
1271
|
+
masked_domain = '***'
|
1272
|
+
|
1273
|
+
return f"{masked_local}@{masked_domain}"
|
1274
|
+
|
1037
1275
|
|
1038
1276
|
# Flask集成装饰器
|
1039
1277
|
def require_auth(auth_manager):
|
@@ -1185,8 +1423,7 @@ def create_permission_checker(auth_manager, required_permissions):
|
|
1185
1423
|
return permission_checker
|
1186
1424
|
|
1187
1425
|
|
1188
|
-
|
1189
|
-
if __name__ == "__main__":
|
1426
|
+
def main():
|
1190
1427
|
# 数据库配置
|
1191
1428
|
db_config = {
|
1192
1429
|
'host': 'localhost',
|
@@ -1207,7 +1444,7 @@ if __name__ == "__main__":
|
|
1207
1444
|
auth_manager = StandaloneAuthManager(db_config, auth_config)
|
1208
1445
|
|
1209
1446
|
# 注册用户
|
1210
|
-
result = auth_manager.register_user('admin', 'password123', 'admin', ['read', 'write', 'admin'])
|
1447
|
+
result = auth_manager.register_user('admin', 'password123', 'admin', ['read', 'write', 'admin'], 'admin@example.com')
|
1211
1448
|
print("注册结果:", result)
|
1212
1449
|
|
1213
1450
|
# 用户认证
|
@@ -1233,4 +1470,9 @@ if __name__ == "__main__":
|
|
1233
1470
|
|
1234
1471
|
# 刷新token
|
1235
1472
|
refresh_result = auth_manager.refresh_access_token(refresh_token, '127.0.0.1', 'Mozilla/5.0...')
|
1236
|
-
print("Token刷新结果:", refresh_result)
|
1473
|
+
print("Token刷新结果:", refresh_result)
|
1474
|
+
|
1475
|
+
|
1476
|
+
# 使用示例
|
1477
|
+
if __name__ == "__main__":
|
1478
|
+
main()
|
mdbq/auth/rate_limiter.py
CHANGED
@@ -94,7 +94,7 @@ import time
|
|
94
94
|
import functools
|
95
95
|
from collections import defaultdict, deque
|
96
96
|
from threading import Lock
|
97
|
-
from typing import
|
97
|
+
from typing import Tuple
|
98
98
|
from dataclasses import dataclass
|
99
99
|
from enum import Enum
|
100
100
|
from flask import request
|
@@ -1,8 +1,8 @@
|
|
1
1
|
mdbq/__init__.py,sha256=Il5Q9ATdX8yXqVxtP_nYqUhExzxPC_qk_WXQ_4h0exg,16
|
2
|
-
mdbq/__version__.py,sha256=
|
2
|
+
mdbq/__version__.py,sha256=pEZWv3rS-jyg5fbC-o4UV_JxSAfkN0r2M3A0QEOiAhg,18
|
3
3
|
mdbq/auth/__init__.py,sha256=pnPMAt63sh1B6kEvmutUuro46zVf2v2YDAG7q-jV_To,24
|
4
|
-
mdbq/auth/auth_backend.py,sha256=
|
5
|
-
mdbq/auth/rate_limiter.py,sha256=
|
4
|
+
mdbq/auth/auth_backend.py,sha256=F5EPVmNRiOs3G2ZOFI9fTdW1OgOjL-yeBonvOj91jZM,59702
|
5
|
+
mdbq/auth/rate_limiter.py,sha256=e7K8pMQlZ1vm1N-c0HBH8tbAwzcmXSRiAl81JNZ369g,26192
|
6
6
|
mdbq/js/__init__.py,sha256=hpMi3_ZKwIWkzc0LnKL-SY9AS-7PYFHq0izYTgEvxjc,30
|
7
7
|
mdbq/js/jc.py,sha256=FOc6HOOTJwnoZLZmgmaE1SQo9rUnVhXmefhKMD2MlDA,13229
|
8
8
|
mdbq/log/__init__.py,sha256=Mpbrav0s0ifLL7lVDAuePEi1hJKiSHhxcv1byBKDl5E,15
|
@@ -33,7 +33,7 @@ mdbq/route/routes.py,sha256=DHJg0eRNi7TKqhCHuu8ia3vdQ8cTKwrTm6mwDBtNboM,19111
|
|
33
33
|
mdbq/selenium/__init__.py,sha256=AKzeEceqZyvqn2dEDoJSzDQnbuENkJSHAlbHAD0u0ZI,10
|
34
34
|
mdbq/selenium/get_driver.py,sha256=1NTlVUE6QsyjTrVVVqTO2LOnYf578ccFWlWnvIXGtic,20903
|
35
35
|
mdbq/spider/__init__.py,sha256=RBMFXGy_jd1HXZhngB2T2XTvJqki8P_Fr-pBcwijnew,18
|
36
|
-
mdbq-4.0.
|
37
|
-
mdbq-4.0.
|
38
|
-
mdbq-4.0.
|
39
|
-
mdbq-4.0.
|
36
|
+
mdbq-4.0.92.dist-info/METADATA,sha256=CvQCEeGo3DkG2bSvOKjDqfL17JWVLN1s_n75zXP7oSs,364
|
37
|
+
mdbq-4.0.92.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
38
|
+
mdbq-4.0.92.dist-info/top_level.txt,sha256=2FQ-uLnCSB-OwFiWntzmwosW3X2Xqsg0ewh1axsaylA,5
|
39
|
+
mdbq-4.0.92.dist-info/RECORD,,
|
File without changes
|
File without changes
|