mdbq 4.0.96__py3-none-any.whl → 4.0.98__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.
Potentially problematic release.
This version of mdbq might be problematic. Click here for more details.
- mdbq/__version__.py +1 -1
- mdbq/auth/auth_backend.py +143 -75
- {mdbq-4.0.96.dist-info → mdbq-4.0.98.dist-info}/METADATA +1 -1
- {mdbq-4.0.96.dist-info → mdbq-4.0.98.dist-info}/RECORD +6 -6
- {mdbq-4.0.96.dist-info → mdbq-4.0.98.dist-info}/WHEEL +0 -0
- {mdbq-4.0.96.dist-info → mdbq-4.0.98.dist-info}/top_level.txt +0 -0
mdbq/__version__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
VERSION = '4.0.
|
|
1
|
+
VERSION = '4.0.98'
|
mdbq/auth/auth_backend.py
CHANGED
|
@@ -223,6 +223,8 @@ class StandaloneAuthManager:
|
|
|
223
223
|
user_id BIGINT UNSIGNED NOT NULL,
|
|
224
224
|
device_id VARCHAR(255) CHARACTER SET ascii NOT NULL,
|
|
225
225
|
device_fingerprint VARCHAR(255) CHARACTER SET ascii NOT NULL,
|
|
226
|
+
login_domain VARCHAR(255) NOT NULL DEFAULT '' COMMENT '登录时的域名',
|
|
227
|
+
session_version INT UNSIGNED NOT NULL DEFAULT 1 COMMENT '会话版本号',
|
|
226
228
|
device_name VARCHAR(255) NOT NULL DEFAULT 'Unknown Device',
|
|
227
229
|
custom_name VARCHAR(255) DEFAULT NULL COMMENT '用户自定义设备名称',
|
|
228
230
|
device_type ENUM('mobile', 'desktop', 'tablet', 'unknown') NOT NULL DEFAULT 'unknown',
|
|
@@ -242,13 +244,15 @@ class StandaloneAuthManager:
|
|
|
242
244
|
trust_level ENUM('trusted', 'normal', 'suspicious') NOT NULL DEFAULT 'normal' COMMENT '设备信任级别',
|
|
243
245
|
|
|
244
246
|
UNIQUE KEY uk_device_sessions_device_id (device_id),
|
|
245
|
-
UNIQUE KEY
|
|
247
|
+
UNIQUE KEY uk_device_sessions_user_fingerprint_domain_version (user_id, device_fingerprint, login_domain, session_version),
|
|
246
248
|
KEY idx_device_sessions_user_id (user_id),
|
|
247
249
|
KEY idx_device_sessions_user_device (user_id, device_id),
|
|
248
250
|
KEY idx_device_sessions_last_activity (last_activity),
|
|
249
251
|
KEY idx_device_sessions_is_active (is_active),
|
|
250
252
|
KEY idx_device_sessions_fingerprint (device_fingerprint),
|
|
251
253
|
KEY idx_device_sessions_trust_level (trust_level),
|
|
254
|
+
KEY idx_device_sessions_login_domain (login_domain),
|
|
255
|
+
KEY idx_device_sessions_user_domain (user_id, login_domain),
|
|
252
256
|
|
|
253
257
|
CONSTRAINT fk_device_sessions_user_id
|
|
254
258
|
FOREIGN KEY (user_id)
|
|
@@ -621,7 +625,7 @@ class StandaloneAuthManager:
|
|
|
621
625
|
except jwt.InvalidTokenError:
|
|
622
626
|
return None
|
|
623
627
|
|
|
624
|
-
def create_or_update_device_session(self, user_id, ip_address, user_agent, device_info=None):
|
|
628
|
+
def create_or_update_device_session(self, user_id, ip_address, user_agent, device_info=None, login_domain=''):
|
|
625
629
|
"""创建或更新设备会话"""
|
|
626
630
|
# 解析用户代理获取基本信息
|
|
627
631
|
parsed_ua = self._parse_user_agent(user_agent)
|
|
@@ -634,7 +638,8 @@ class StandaloneAuthManager:
|
|
|
634
638
|
**(device_info or {})
|
|
635
639
|
}
|
|
636
640
|
|
|
637
|
-
|
|
641
|
+
# 生成包含域名的设备指纹
|
|
642
|
+
device_fingerprint = self._generate_device_fingerprint(full_device_info, login_domain)
|
|
638
643
|
device_id = secrets.token_urlsafe(32)
|
|
639
644
|
|
|
640
645
|
conn = self.pool.connection()
|
|
@@ -643,79 +648,63 @@ class StandaloneAuthManager:
|
|
|
643
648
|
try:
|
|
644
649
|
current_time_utc = datetime.now(timezone.utc)
|
|
645
650
|
|
|
646
|
-
#
|
|
651
|
+
# 软删除:将该用户在该域名下的相同设备指纹的所有活跃会话标记为非活跃
|
|
647
652
|
cursor.execute('''
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
653
|
+
UPDATE device_sessions
|
|
654
|
+
SET is_active = 0
|
|
655
|
+
WHERE user_id = %s AND device_fingerprint = %s AND login_domain = %s AND is_active = 1
|
|
656
|
+
''', (user_id, device_fingerprint, login_domain))
|
|
651
657
|
|
|
652
|
-
|
|
658
|
+
# 获取下一个版本号
|
|
659
|
+
cursor.execute('''
|
|
660
|
+
SELECT COALESCE(MAX(session_version), 0) + 1 as next_version
|
|
661
|
+
FROM device_sessions
|
|
662
|
+
WHERE user_id = %s AND device_fingerprint = %s AND login_domain = %s
|
|
663
|
+
''', (user_id, device_fingerprint, login_domain))
|
|
653
664
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
''', (ip_address, user_agent, current_time_utc,
|
|
667
|
-
full_device_info.get('device_name', 'Unknown Device'),
|
|
668
|
-
full_device_info.get('device_type', 'unknown'),
|
|
669
|
-
full_device_info.get('platform'),
|
|
670
|
-
full_device_info.get('browser'),
|
|
671
|
-
full_device_info.get('browser_version'),
|
|
672
|
-
full_device_info.get('screen_resolution'),
|
|
673
|
-
full_device_info.get('timezone_offset'),
|
|
674
|
-
full_device_info.get('language'),
|
|
675
|
-
full_device_info.get('hardware_concurrency'),
|
|
676
|
-
device_session_id))
|
|
677
|
-
else:
|
|
678
|
-
# 检查设备数量限制
|
|
679
|
-
cursor.execute('''
|
|
680
|
-
SELECT COUNT(*) as active_count FROM device_sessions
|
|
681
|
-
WHERE user_id = %s AND is_active = 1
|
|
682
|
-
''', (user_id,))
|
|
683
|
-
|
|
684
|
-
active_count = cursor.fetchone()['active_count']
|
|
685
|
-
|
|
686
|
-
if active_count >= self.auth_config['max_concurrent_devices']:
|
|
687
|
-
# 踢出最旧的设备
|
|
688
|
-
cursor.execute('''
|
|
689
|
-
SELECT id FROM device_sessions
|
|
690
|
-
WHERE user_id = %s AND is_active = 1
|
|
691
|
-
ORDER BY last_activity ASC
|
|
692
|
-
LIMIT %s
|
|
693
|
-
''', (user_id, active_count - self.auth_config['max_concurrent_devices'] + 1))
|
|
694
|
-
|
|
695
|
-
old_sessions = cursor.fetchall()
|
|
696
|
-
for old_session in old_sessions:
|
|
697
|
-
self._revoke_device_session(cursor, old_session['id'], 'device_limit')
|
|
698
|
-
|
|
699
|
-
# 创建新设备会话
|
|
665
|
+
next_version = cursor.fetchone()['next_version']
|
|
666
|
+
|
|
667
|
+
# 检查设备数量限制
|
|
668
|
+
cursor.execute('''
|
|
669
|
+
SELECT COUNT(*) as active_count FROM device_sessions
|
|
670
|
+
WHERE user_id = %s AND is_active = 1
|
|
671
|
+
''', (user_id,))
|
|
672
|
+
|
|
673
|
+
active_count = cursor.fetchone()['active_count']
|
|
674
|
+
|
|
675
|
+
if active_count >= self.auth_config['max_concurrent_devices']:
|
|
676
|
+
# 踢出最旧的设备
|
|
700
677
|
cursor.execute('''
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
''', (user_id, device_id, device_fingerprint,
|
|
707
|
-
full_device_info.get('device_name', 'Unknown Device'),
|
|
708
|
-
full_device_info.get('device_type', 'unknown'),
|
|
709
|
-
full_device_info.get('platform'),
|
|
710
|
-
full_device_info.get('browser'),
|
|
711
|
-
full_device_info.get('browser_version'),
|
|
712
|
-
full_device_info.get('screen_resolution'),
|
|
713
|
-
full_device_info.get('timezone_offset'),
|
|
714
|
-
full_device_info.get('language'),
|
|
715
|
-
full_device_info.get('hardware_concurrency'),
|
|
716
|
-
ip_address, ip_address, user_agent, current_time_utc))
|
|
678
|
+
SELECT id FROM device_sessions
|
|
679
|
+
WHERE user_id = %s AND is_active = 1
|
|
680
|
+
ORDER BY last_activity ASC
|
|
681
|
+
LIMIT %s
|
|
682
|
+
''', (user_id, active_count - self.auth_config['max_concurrent_devices'] + 1))
|
|
717
683
|
|
|
718
|
-
|
|
684
|
+
old_sessions = cursor.fetchall()
|
|
685
|
+
for old_session in old_sessions:
|
|
686
|
+
self._revoke_device_session(cursor, old_session['id'], 'device_limit')
|
|
687
|
+
|
|
688
|
+
# 创建新设备会话(带版本号)
|
|
689
|
+
cursor.execute('''
|
|
690
|
+
INSERT INTO device_sessions (
|
|
691
|
+
user_id, device_id, device_fingerprint, login_domain, session_version, device_name, device_type,
|
|
692
|
+
platform, browser, browser_version, screen_resolution, timezone_offset,
|
|
693
|
+
language, hardware_concurrency, current_ip, first_ip, user_agent, last_activity
|
|
694
|
+
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
|
695
|
+
''', (user_id, device_id, device_fingerprint, login_domain, next_version,
|
|
696
|
+
full_device_info.get('device_name', 'Unknown Device'),
|
|
697
|
+
full_device_info.get('device_type', 'unknown'),
|
|
698
|
+
full_device_info.get('platform'),
|
|
699
|
+
full_device_info.get('browser'),
|
|
700
|
+
full_device_info.get('browser_version'),
|
|
701
|
+
full_device_info.get('screen_resolution'),
|
|
702
|
+
full_device_info.get('timezone_offset'),
|
|
703
|
+
full_device_info.get('language'),
|
|
704
|
+
full_device_info.get('hardware_concurrency'),
|
|
705
|
+
ip_address, ip_address, user_agent, current_time_utc))
|
|
706
|
+
|
|
707
|
+
device_session_id = cursor.lastrowid
|
|
719
708
|
|
|
720
709
|
return device_session_id, device_id, full_device_info.get('device_name', 'Unknown Device')
|
|
721
710
|
|
|
@@ -1253,9 +1242,9 @@ class StandaloneAuthManager:
|
|
|
1253
1242
|
WHERE id = %s
|
|
1254
1243
|
''', (device_session_id,))
|
|
1255
1244
|
|
|
1256
|
-
def _generate_device_fingerprint(self, device_info):
|
|
1245
|
+
def _generate_device_fingerprint(self, device_info, login_domain=''):
|
|
1257
1246
|
"""
|
|
1258
|
-
|
|
1247
|
+
生成稳定的设备指纹(包含登录域名)
|
|
1259
1248
|
|
|
1260
1249
|
Args:
|
|
1261
1250
|
device_info (dict): 设备信息
|
|
@@ -1265,8 +1254,9 @@ class StandaloneAuthManager:
|
|
|
1265
1254
|
- language: 浏览器语言
|
|
1266
1255
|
- hardware_concurrency: CPU核心数
|
|
1267
1256
|
- platform: 平台信息
|
|
1257
|
+
login_domain (str): 登录时的域名
|
|
1268
1258
|
"""
|
|
1269
|
-
#
|
|
1259
|
+
# 提取稳定的设备特征(包含登录域名)
|
|
1270
1260
|
stable_features = [
|
|
1271
1261
|
device_info.get('user_agent', ''),
|
|
1272
1262
|
device_info.get('screen_resolution', ''),
|
|
@@ -1274,6 +1264,7 @@ class StandaloneAuthManager:
|
|
|
1274
1264
|
device_info.get('language', ''),
|
|
1275
1265
|
str(device_info.get('hardware_concurrency', 0)),
|
|
1276
1266
|
device_info.get('platform', ''),
|
|
1267
|
+
login_domain or '', # 登录域名作为设备指纹的一部分
|
|
1277
1268
|
]
|
|
1278
1269
|
|
|
1279
1270
|
# 过滤空值并连接
|
|
@@ -1383,7 +1374,7 @@ class StandaloneAuthManager:
|
|
|
1383
1374
|
|
|
1384
1375
|
try:
|
|
1385
1376
|
cursor.execute('''
|
|
1386
|
-
SELECT device_id, device_fingerprint, device_name, custom_name, device_type,
|
|
1377
|
+
SELECT device_id, device_fingerprint, login_domain, device_name, custom_name, device_type,
|
|
1387
1378
|
platform, browser, browser_version, screen_resolution, timezone_offset,
|
|
1388
1379
|
language, hardware_concurrency, current_ip, first_ip,
|
|
1389
1380
|
last_activity, created_at, is_active, trust_level
|
|
@@ -1408,6 +1399,7 @@ class StandaloneAuthManager:
|
|
|
1408
1399
|
'device_id': device['device_id'],
|
|
1409
1400
|
'device_name': display_name,
|
|
1410
1401
|
'custom_name': device['custom_name'],
|
|
1402
|
+
'login_domain': device['login_domain'],
|
|
1411
1403
|
'device_type': device['device_type'],
|
|
1412
1404
|
'platform': device['platform'],
|
|
1413
1405
|
'browser': device['browser'],
|
|
@@ -1671,6 +1663,82 @@ class StandaloneAuthManager:
|
|
|
1671
1663
|
cursor.close()
|
|
1672
1664
|
conn.close()
|
|
1673
1665
|
|
|
1666
|
+
def get_device_session_history(self, user_id, device_fingerprint, login_domain, limit=10):
|
|
1667
|
+
"""获取设备会话历史记录"""
|
|
1668
|
+
conn = self.pool.connection()
|
|
1669
|
+
cursor = conn.cursor()
|
|
1670
|
+
|
|
1671
|
+
try:
|
|
1672
|
+
cursor.execute('''
|
|
1673
|
+
SELECT device_id, session_version, device_name, current_ip, first_ip,
|
|
1674
|
+
last_activity, created_at, is_active, trust_level
|
|
1675
|
+
FROM device_sessions
|
|
1676
|
+
WHERE user_id = %s AND device_fingerprint = %s AND login_domain = %s
|
|
1677
|
+
ORDER BY session_version DESC
|
|
1678
|
+
LIMIT %s
|
|
1679
|
+
''', (user_id, device_fingerprint, login_domain, limit))
|
|
1680
|
+
|
|
1681
|
+
sessions = cursor.fetchall()
|
|
1682
|
+
|
|
1683
|
+
return [
|
|
1684
|
+
{
|
|
1685
|
+
'device_id': session['device_id'],
|
|
1686
|
+
'session_version': session['session_version'],
|
|
1687
|
+
'device_name': session['device_name'],
|
|
1688
|
+
'current_ip': session['current_ip'],
|
|
1689
|
+
'first_ip': session['first_ip'],
|
|
1690
|
+
'last_activity': session['last_activity'].isoformat() if session['last_activity'] else None,
|
|
1691
|
+
'created_at': session['created_at'].isoformat() if session['created_at'] else None,
|
|
1692
|
+
'is_active': session['is_active'],
|
|
1693
|
+
'trust_level': session['trust_level']
|
|
1694
|
+
}
|
|
1695
|
+
for session in sessions
|
|
1696
|
+
]
|
|
1697
|
+
|
|
1698
|
+
finally:
|
|
1699
|
+
cursor.close()
|
|
1700
|
+
conn.close()
|
|
1701
|
+
|
|
1702
|
+
def cleanup_old_device_sessions(self, user_id=None, days_threshold=90):
|
|
1703
|
+
"""清理旧的非活跃设备会话记录"""
|
|
1704
|
+
conn = self.pool.connection()
|
|
1705
|
+
cursor = conn.cursor()
|
|
1706
|
+
|
|
1707
|
+
try:
|
|
1708
|
+
current_time_utc = datetime.now(timezone.utc)
|
|
1709
|
+
threshold_time = current_time_utc - timedelta(days=days_threshold)
|
|
1710
|
+
|
|
1711
|
+
if user_id:
|
|
1712
|
+
# 清理特定用户的旧记录
|
|
1713
|
+
cursor.execute('''
|
|
1714
|
+
DELETE FROM device_sessions
|
|
1715
|
+
WHERE user_id = %s AND is_active = 0 AND last_activity < %s
|
|
1716
|
+
''', (user_id, threshold_time))
|
|
1717
|
+
else:
|
|
1718
|
+
# 清理所有用户的旧记录
|
|
1719
|
+
cursor.execute('''
|
|
1720
|
+
DELETE FROM device_sessions
|
|
1721
|
+
WHERE is_active = 0 AND last_activity < %s
|
|
1722
|
+
''', (threshold_time,))
|
|
1723
|
+
|
|
1724
|
+
cleaned_count = cursor.rowcount
|
|
1725
|
+
|
|
1726
|
+
return {
|
|
1727
|
+
'success': True,
|
|
1728
|
+
'cleaned_count': cleaned_count,
|
|
1729
|
+
'message': f'成功清理 {cleaned_count} 条旧设备会话记录'
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
except Exception as e:
|
|
1733
|
+
return {
|
|
1734
|
+
'success': False,
|
|
1735
|
+
'cleaned_count': 0,
|
|
1736
|
+
'message': f'清理旧设备会话记录失败: {str(e)}'
|
|
1737
|
+
}
|
|
1738
|
+
finally:
|
|
1739
|
+
cursor.close()
|
|
1740
|
+
conn.close()
|
|
1741
|
+
|
|
1674
1742
|
|
|
1675
1743
|
# Flask集成装饰器
|
|
1676
1744
|
def require_auth(auth_manager):
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
mdbq/__init__.py,sha256=Il5Q9ATdX8yXqVxtP_nYqUhExzxPC_qk_WXQ_4h0exg,16
|
|
2
|
-
mdbq/__version__.py,sha256=
|
|
2
|
+
mdbq/__version__.py,sha256=XOK57sJ__3m3ANkzwBW5d22bjMTf9V9OKO9SEFWAn7Q,18
|
|
3
3
|
mdbq/auth/__init__.py,sha256=pnPMAt63sh1B6kEvmutUuro46zVf2v2YDAG7q-jV_To,24
|
|
4
|
-
mdbq/auth/auth_backend.py,sha256=
|
|
4
|
+
mdbq/auth/auth_backend.py,sha256=BzkuHiWw_h7FR5kl4EvwO0NuoxovSr93Bkw2L9hmaHU,79297
|
|
5
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
|
|
@@ -33,7 +33,7 @@ mdbq/route/routes.py,sha256=QVGfTvDgu0CpcKCvk1ra74H8uojgqTLUav1fnVAqLEA,29433
|
|
|
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.98.dist-info/METADATA,sha256=Af7ORPnWNs024LBtSG_m_lgr26HIqJIPlf5UDMC9bxY,364
|
|
37
|
+
mdbq-4.0.98.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
38
|
+
mdbq-4.0.98.dist-info/top_level.txt,sha256=2FQ-uLnCSB-OwFiWntzmwosW3X2Xqsg0ewh1axsaylA,5
|
|
39
|
+
mdbq-4.0.98.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|