mdbq 4.0.95__py3-none-any.whl → 4.0.97__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 +180 -29
- {mdbq-4.0.95.dist-info → mdbq-4.0.97.dist-info}/METADATA +1 -1
- {mdbq-4.0.95.dist-info → mdbq-4.0.97.dist-info}/RECORD +6 -6
- {mdbq-4.0.95.dist-info → mdbq-4.0.97.dist-info}/WHEEL +0 -0
- {mdbq-4.0.95.dist-info → mdbq-4.0.97.dist-info}/top_level.txt +0 -0
mdbq/__version__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
VERSION = '4.0.
|
|
1
|
+
VERSION = '4.0.97'
|
mdbq/auth/auth_backend.py
CHANGED
|
@@ -221,24 +221,37 @@ class StandaloneAuthManager:
|
|
|
221
221
|
CREATE TABLE IF NOT EXISTS device_sessions (
|
|
222
222
|
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
|
223
223
|
user_id BIGINT UNSIGNED NOT NULL,
|
|
224
|
-
device_id VARCHAR(
|
|
225
|
-
device_fingerprint VARCHAR(
|
|
226
|
-
|
|
224
|
+
device_id VARCHAR(255) CHARACTER SET ascii NOT NULL,
|
|
225
|
+
device_fingerprint VARCHAR(255) CHARACTER SET ascii NOT NULL,
|
|
226
|
+
login_domain VARCHAR(255) NOT NULL DEFAULT '' COMMENT '登录时的域名',
|
|
227
|
+
device_name VARCHAR(255) NOT NULL DEFAULT 'Unknown Device',
|
|
228
|
+
custom_name VARCHAR(255) DEFAULT NULL COMMENT '用户自定义设备名称',
|
|
227
229
|
device_type ENUM('mobile', 'desktop', 'tablet', 'unknown') NOT NULL DEFAULT 'unknown',
|
|
228
230
|
platform VARCHAR(50) DEFAULT NULL,
|
|
229
231
|
browser VARCHAR(50) DEFAULT NULL,
|
|
230
|
-
|
|
232
|
+
browser_version VARCHAR(40) DEFAULT NULL,
|
|
233
|
+
screen_resolution VARCHAR(40) DEFAULT NULL COMMENT '屏幕分辨率',
|
|
234
|
+
timezone_offset INT DEFAULT NULL COMMENT '时区偏移(分钟)',
|
|
235
|
+
language VARCHAR(20) DEFAULT NULL COMMENT '浏览器语言',
|
|
236
|
+
hardware_concurrency TINYINT UNSIGNED DEFAULT NULL COMMENT 'CPU核心数',
|
|
237
|
+
current_ip VARCHAR(45) NOT NULL COMMENT '当前IP地址',
|
|
238
|
+
first_ip VARCHAR(45) NOT NULL COMMENT '首次登录IP',
|
|
231
239
|
user_agent TEXT NOT NULL,
|
|
232
240
|
last_activity TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
|
233
241
|
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
|
234
242
|
is_active TINYINT(1) NOT NULL DEFAULT 1,
|
|
243
|
+
trust_level ENUM('trusted', 'normal', 'suspicious') NOT NULL DEFAULT 'normal' COMMENT '设备信任级别',
|
|
235
244
|
|
|
236
245
|
UNIQUE KEY uk_device_sessions_device_id (device_id),
|
|
246
|
+
UNIQUE KEY uk_device_sessions_user_fingerprint_domain (user_id, device_fingerprint, login_domain),
|
|
237
247
|
KEY idx_device_sessions_user_id (user_id),
|
|
238
248
|
KEY idx_device_sessions_user_device (user_id, device_id),
|
|
239
249
|
KEY idx_device_sessions_last_activity (last_activity),
|
|
240
250
|
KEY idx_device_sessions_is_active (is_active),
|
|
241
251
|
KEY idx_device_sessions_fingerprint (device_fingerprint),
|
|
252
|
+
KEY idx_device_sessions_trust_level (trust_level),
|
|
253
|
+
KEY idx_device_sessions_login_domain (login_domain),
|
|
254
|
+
KEY idx_device_sessions_user_domain (user_id, login_domain),
|
|
242
255
|
|
|
243
256
|
CONSTRAINT fk_device_sessions_user_id
|
|
244
257
|
FOREIGN KEY (user_id)
|
|
@@ -611,10 +624,21 @@ class StandaloneAuthManager:
|
|
|
611
624
|
except jwt.InvalidTokenError:
|
|
612
625
|
return None
|
|
613
626
|
|
|
614
|
-
def create_or_update_device_session(self, user_id, ip_address, user_agent):
|
|
627
|
+
def create_or_update_device_session(self, user_id, ip_address, user_agent, device_info=None, login_domain=''):
|
|
615
628
|
"""创建或更新设备会话"""
|
|
616
|
-
|
|
617
|
-
|
|
629
|
+
# 解析用户代理获取基本信息
|
|
630
|
+
parsed_ua = self._parse_user_agent(user_agent)
|
|
631
|
+
|
|
632
|
+
# 合并设备信息
|
|
633
|
+
full_device_info = {
|
|
634
|
+
'user_agent': user_agent,
|
|
635
|
+
'platform': parsed_ua.get('platform'),
|
|
636
|
+
**parsed_ua,
|
|
637
|
+
**(device_info or {})
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
# 生成包含域名的设备指纹
|
|
641
|
+
device_fingerprint = self._generate_device_fingerprint(full_device_info, login_domain)
|
|
618
642
|
device_id = secrets.token_urlsafe(32)
|
|
619
643
|
|
|
620
644
|
conn = self.pool.connection()
|
|
@@ -623,11 +647,11 @@ class StandaloneAuthManager:
|
|
|
623
647
|
try:
|
|
624
648
|
current_time_utc = datetime.now(timezone.utc)
|
|
625
649
|
|
|
626
|
-
#
|
|
650
|
+
# 检查设备是否已存在(包含域名匹配)
|
|
627
651
|
cursor.execute('''
|
|
628
652
|
SELECT id, device_id FROM device_sessions
|
|
629
|
-
WHERE user_id = %s AND device_fingerprint = %s AND is_active = 1
|
|
630
|
-
''', (user_id, device_fingerprint))
|
|
653
|
+
WHERE user_id = %s AND device_fingerprint = %s AND login_domain = %s AND is_active = 1
|
|
654
|
+
''', (user_id, device_fingerprint, login_domain))
|
|
631
655
|
|
|
632
656
|
existing_session = cursor.fetchone()
|
|
633
657
|
|
|
@@ -638,12 +662,23 @@ class StandaloneAuthManager:
|
|
|
638
662
|
|
|
639
663
|
cursor.execute('''
|
|
640
664
|
UPDATE device_sessions
|
|
641
|
-
SET
|
|
642
|
-
device_name = %s, device_type = %s, platform = %s, browser = %s
|
|
665
|
+
SET current_ip = %s, user_agent = %s, last_activity = %s,
|
|
666
|
+
device_name = %s, device_type = %s, platform = %s, browser = %s,
|
|
667
|
+
browser_version = %s, screen_resolution = %s, timezone_offset = %s,
|
|
668
|
+
language = %s, hardware_concurrency = %s, login_domain = %s
|
|
643
669
|
WHERE id = %s
|
|
644
670
|
''', (ip_address, user_agent, current_time_utc,
|
|
645
|
-
|
|
646
|
-
|
|
671
|
+
full_device_info.get('device_name', 'Unknown Device'),
|
|
672
|
+
full_device_info.get('device_type', 'unknown'),
|
|
673
|
+
full_device_info.get('platform'),
|
|
674
|
+
full_device_info.get('browser'),
|
|
675
|
+
full_device_info.get('browser_version'),
|
|
676
|
+
full_device_info.get('screen_resolution'),
|
|
677
|
+
full_device_info.get('timezone_offset'),
|
|
678
|
+
full_device_info.get('language'),
|
|
679
|
+
full_device_info.get('hardware_concurrency'),
|
|
680
|
+
login_domain,
|
|
681
|
+
device_session_id))
|
|
647
682
|
else:
|
|
648
683
|
# 检查设备数量限制
|
|
649
684
|
cursor.execute('''
|
|
@@ -669,16 +704,25 @@ class StandaloneAuthManager:
|
|
|
669
704
|
# 创建新设备会话
|
|
670
705
|
cursor.execute('''
|
|
671
706
|
INSERT INTO device_sessions (
|
|
672
|
-
user_id, device_id, device_fingerprint, device_name, device_type,
|
|
673
|
-
platform, browser,
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
707
|
+
user_id, device_id, device_fingerprint, login_domain, device_name, device_type,
|
|
708
|
+
platform, browser, browser_version, screen_resolution, timezone_offset,
|
|
709
|
+
language, hardware_concurrency, current_ip, first_ip, user_agent, last_activity
|
|
710
|
+
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
|
711
|
+
''', (user_id, device_id, device_fingerprint, login_domain,
|
|
712
|
+
full_device_info.get('device_name', 'Unknown Device'),
|
|
713
|
+
full_device_info.get('device_type', 'unknown'),
|
|
714
|
+
full_device_info.get('platform'),
|
|
715
|
+
full_device_info.get('browser'),
|
|
716
|
+
full_device_info.get('browser_version'),
|
|
717
|
+
full_device_info.get('screen_resolution'),
|
|
718
|
+
full_device_info.get('timezone_offset'),
|
|
719
|
+
full_device_info.get('language'),
|
|
720
|
+
full_device_info.get('hardware_concurrency'),
|
|
721
|
+
ip_address, ip_address, user_agent, current_time_utc))
|
|
678
722
|
|
|
679
723
|
device_session_id = cursor.lastrowid
|
|
680
724
|
|
|
681
|
-
return device_session_id, device_id,
|
|
725
|
+
return device_session_id, device_id, full_device_info.get('device_name', 'Unknown Device')
|
|
682
726
|
|
|
683
727
|
finally:
|
|
684
728
|
cursor.close()
|
|
@@ -1214,10 +1258,36 @@ class StandaloneAuthManager:
|
|
|
1214
1258
|
WHERE id = %s
|
|
1215
1259
|
''', (device_session_id,))
|
|
1216
1260
|
|
|
1217
|
-
def _generate_device_fingerprint(self,
|
|
1218
|
-
"""
|
|
1219
|
-
|
|
1220
|
-
|
|
1261
|
+
def _generate_device_fingerprint(self, device_info, login_domain=''):
|
|
1262
|
+
"""
|
|
1263
|
+
生成稳定的设备指纹(包含登录域名)
|
|
1264
|
+
|
|
1265
|
+
Args:
|
|
1266
|
+
device_info (dict): 设备信息
|
|
1267
|
+
- user_agent: 用户代理
|
|
1268
|
+
- screen_resolution: 屏幕分辨率
|
|
1269
|
+
- timezone_offset: 时区偏移
|
|
1270
|
+
- language: 浏览器语言
|
|
1271
|
+
- hardware_concurrency: CPU核心数
|
|
1272
|
+
- platform: 平台信息
|
|
1273
|
+
login_domain (str): 登录时的域名
|
|
1274
|
+
"""
|
|
1275
|
+
# 提取稳定的设备特征(包含登录域名)
|
|
1276
|
+
stable_features = [
|
|
1277
|
+
device_info.get('user_agent', ''),
|
|
1278
|
+
device_info.get('screen_resolution', ''),
|
|
1279
|
+
str(device_info.get('timezone_offset', 0)),
|
|
1280
|
+
device_info.get('language', ''),
|
|
1281
|
+
str(device_info.get('hardware_concurrency', 0)),
|
|
1282
|
+
device_info.get('platform', ''),
|
|
1283
|
+
login_domain or '', # 登录域名作为设备指纹的一部分
|
|
1284
|
+
]
|
|
1285
|
+
|
|
1286
|
+
# 过滤空值并连接
|
|
1287
|
+
fingerprint_data = ':'.join(filter(None, stable_features))
|
|
1288
|
+
|
|
1289
|
+
# 生成SHA256哈希
|
|
1290
|
+
return hashlib.sha256(fingerprint_data.encode('utf-8')).hexdigest()[:32]
|
|
1221
1291
|
|
|
1222
1292
|
def _parse_user_agent(self, user_agent):
|
|
1223
1293
|
"""解析User-Agent获取设备信息"""
|
|
@@ -1320,8 +1390,10 @@ class StandaloneAuthManager:
|
|
|
1320
1390
|
|
|
1321
1391
|
try:
|
|
1322
1392
|
cursor.execute('''
|
|
1323
|
-
SELECT device_id, device_fingerprint, device_name,
|
|
1324
|
-
|
|
1393
|
+
SELECT device_id, device_fingerprint, login_domain, device_name, custom_name, device_type,
|
|
1394
|
+
platform, browser, browser_version, screen_resolution, timezone_offset,
|
|
1395
|
+
language, hardware_concurrency, current_ip, first_ip,
|
|
1396
|
+
last_activity, created_at, is_active, trust_level
|
|
1325
1397
|
FROM device_sessions
|
|
1326
1398
|
WHERE user_id = %s AND is_active = 1
|
|
1327
1399
|
ORDER BY last_activity DESC
|
|
@@ -1336,13 +1408,25 @@ class StandaloneAuthManager:
|
|
|
1336
1408
|
if is_current:
|
|
1337
1409
|
current_device_id = device['device_id']
|
|
1338
1410
|
|
|
1411
|
+
# 生成显示名称(优先使用自定义名称)
|
|
1412
|
+
display_name = device['custom_name'] or device['device_name']
|
|
1413
|
+
|
|
1339
1414
|
devices_list.append({
|
|
1340
1415
|
'device_id': device['device_id'],
|
|
1341
|
-
'device_name':
|
|
1416
|
+
'device_name': display_name,
|
|
1417
|
+
'custom_name': device['custom_name'],
|
|
1418
|
+
'login_domain': device['login_domain'],
|
|
1342
1419
|
'device_type': device['device_type'],
|
|
1343
1420
|
'platform': device['platform'],
|
|
1344
1421
|
'browser': device['browser'],
|
|
1345
|
-
'
|
|
1422
|
+
'browser_version': device['browser_version'],
|
|
1423
|
+
'screen_resolution': device['screen_resolution'],
|
|
1424
|
+
'timezone_offset': device['timezone_offset'],
|
|
1425
|
+
'language': device['language'],
|
|
1426
|
+
'hardware_concurrency': device['hardware_concurrency'],
|
|
1427
|
+
'current_ip': device['current_ip'],
|
|
1428
|
+
'first_ip': device['first_ip'],
|
|
1429
|
+
'trust_level': device['trust_level'],
|
|
1346
1430
|
'last_activity': device['last_activity'].isoformat() if device['last_activity'] else None,
|
|
1347
1431
|
'created_at': device['created_at'].isoformat() if device['created_at'] else None,
|
|
1348
1432
|
'is_current': is_current
|
|
@@ -1528,6 +1612,73 @@ class StandaloneAuthManager:
|
|
|
1528
1612
|
cursor.close()
|
|
1529
1613
|
conn.close()
|
|
1530
1614
|
|
|
1615
|
+
def rename_device(self, user_id, device_id, custom_name):
|
|
1616
|
+
"""重命名设备"""
|
|
1617
|
+
conn = self.pool.connection()
|
|
1618
|
+
cursor = conn.cursor()
|
|
1619
|
+
|
|
1620
|
+
try:
|
|
1621
|
+
# 验证设备是否属于该用户
|
|
1622
|
+
cursor.execute('''
|
|
1623
|
+
SELECT id FROM device_sessions
|
|
1624
|
+
WHERE user_id = %s AND device_id = %s AND is_active = 1
|
|
1625
|
+
''', (user_id, device_id))
|
|
1626
|
+
|
|
1627
|
+
device = cursor.fetchone()
|
|
1628
|
+
|
|
1629
|
+
if not device:
|
|
1630
|
+
return {'success': False, 'message': '设备不存在或已失效'}
|
|
1631
|
+
|
|
1632
|
+
# 更新自定义名称
|
|
1633
|
+
cursor.execute('''
|
|
1634
|
+
UPDATE device_sessions
|
|
1635
|
+
SET custom_name = %s
|
|
1636
|
+
WHERE id = %s
|
|
1637
|
+
''', (custom_name.strip() if custom_name else None, device['id']))
|
|
1638
|
+
|
|
1639
|
+
return {'success': True, 'message': '设备重命名成功'}
|
|
1640
|
+
|
|
1641
|
+
except Exception as e:
|
|
1642
|
+
return {'success': False, 'message': f'设备重命名失败: {str(e)}'}
|
|
1643
|
+
finally:
|
|
1644
|
+
cursor.close()
|
|
1645
|
+
conn.close()
|
|
1646
|
+
|
|
1647
|
+
def set_device_trust_level(self, user_id, device_id, trust_level):
|
|
1648
|
+
"""设置设备信任级别"""
|
|
1649
|
+
if trust_level not in ['trusted', 'normal', 'suspicious']:
|
|
1650
|
+
return {'success': False, 'message': '无效的信任级别'}
|
|
1651
|
+
|
|
1652
|
+
conn = self.pool.connection()
|
|
1653
|
+
cursor = conn.cursor()
|
|
1654
|
+
|
|
1655
|
+
try:
|
|
1656
|
+
# 验证设备是否属于该用户
|
|
1657
|
+
cursor.execute('''
|
|
1658
|
+
SELECT id FROM device_sessions
|
|
1659
|
+
WHERE user_id = %s AND device_id = %s AND is_active = 1
|
|
1660
|
+
''', (user_id, device_id))
|
|
1661
|
+
|
|
1662
|
+
device = cursor.fetchone()
|
|
1663
|
+
|
|
1664
|
+
if not device:
|
|
1665
|
+
return {'success': False, 'message': '设备不存在或已失效'}
|
|
1666
|
+
|
|
1667
|
+
# 更新信任级别
|
|
1668
|
+
cursor.execute('''
|
|
1669
|
+
UPDATE device_sessions
|
|
1670
|
+
SET trust_level = %s
|
|
1671
|
+
WHERE id = %s
|
|
1672
|
+
''', (trust_level, device['id']))
|
|
1673
|
+
|
|
1674
|
+
return {'success': True, 'message': '设备信任级别更新成功'}
|
|
1675
|
+
|
|
1676
|
+
except Exception as e:
|
|
1677
|
+
return {'success': False, 'message': f'设备信任级别更新失败: {str(e)}'}
|
|
1678
|
+
finally:
|
|
1679
|
+
cursor.close()
|
|
1680
|
+
conn.close()
|
|
1681
|
+
|
|
1531
1682
|
|
|
1532
1683
|
# Flask集成装饰器
|
|
1533
1684
|
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=Wugnfrfi17MIisDJ2bCMK15l65XkooAlWs6nIab3BTw,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=FYsBS2fvowfdfDkl4i2k64JwgN5PWOwVqNGjRoqtCLE,77215
|
|
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.97.dist-info/METADATA,sha256=6P2tfV2sppjE7Y0niKXl1bqeYLa_bYfcLqhSMNr1ygg,364
|
|
37
|
+
mdbq-4.0.97.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
38
|
+
mdbq-4.0.97.dist-info/top_level.txt,sha256=2FQ-uLnCSB-OwFiWntzmwosW3X2Xqsg0ewh1axsaylA,5
|
|
39
|
+
mdbq-4.0.97.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|