mdbq 4.0.95__py3-none-any.whl → 4.0.96__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 CHANGED
@@ -1 +1 @@
1
- VERSION = '4.0.95'
1
+ VERSION = '4.0.96'
mdbq/auth/auth_backend.py CHANGED
@@ -221,24 +221,34 @@ 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(64) CHARACTER SET ascii NOT NULL,
225
- device_fingerprint VARCHAR(128) CHARACTER SET ascii NOT NULL,
226
- device_name VARCHAR(100) NOT NULL DEFAULT 'Unknown Device',
224
+ device_id VARCHAR(255) CHARACTER SET ascii NOT NULL,
225
+ device_fingerprint VARCHAR(255) CHARACTER SET ascii NOT NULL,
226
+ device_name VARCHAR(255) NOT NULL DEFAULT 'Unknown Device',
227
+ custom_name VARCHAR(255) DEFAULT NULL COMMENT '用户自定义设备名称',
227
228
  device_type ENUM('mobile', 'desktop', 'tablet', 'unknown') NOT NULL DEFAULT 'unknown',
228
229
  platform VARCHAR(50) DEFAULT NULL,
229
230
  browser VARCHAR(50) DEFAULT NULL,
230
- ip_address VARCHAR(45) NOT NULL,
231
+ browser_version VARCHAR(40) DEFAULT NULL,
232
+ screen_resolution VARCHAR(40) DEFAULT NULL COMMENT '屏幕分辨率',
233
+ timezone_offset INT DEFAULT NULL COMMENT '时区偏移(分钟)',
234
+ language VARCHAR(20) DEFAULT NULL COMMENT '浏览器语言',
235
+ hardware_concurrency TINYINT UNSIGNED DEFAULT NULL COMMENT 'CPU核心数',
236
+ current_ip VARCHAR(45) NOT NULL COMMENT '当前IP地址',
237
+ first_ip VARCHAR(45) NOT NULL COMMENT '首次登录IP',
231
238
  user_agent TEXT NOT NULL,
232
239
  last_activity TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
233
240
  created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
234
241
  is_active TINYINT(1) NOT NULL DEFAULT 1,
242
+ trust_level ENUM('trusted', 'normal', 'suspicious') NOT NULL DEFAULT 'normal' COMMENT '设备信任级别',
235
243
 
236
244
  UNIQUE KEY uk_device_sessions_device_id (device_id),
245
+ UNIQUE KEY uk_device_sessions_user_fingerprint (user_id, device_fingerprint),
237
246
  KEY idx_device_sessions_user_id (user_id),
238
247
  KEY idx_device_sessions_user_device (user_id, device_id),
239
248
  KEY idx_device_sessions_last_activity (last_activity),
240
249
  KEY idx_device_sessions_is_active (is_active),
241
250
  KEY idx_device_sessions_fingerprint (device_fingerprint),
251
+ KEY idx_device_sessions_trust_level (trust_level),
242
252
 
243
253
  CONSTRAINT fk_device_sessions_user_id
244
254
  FOREIGN KEY (user_id)
@@ -611,10 +621,20 @@ class StandaloneAuthManager:
611
621
  except jwt.InvalidTokenError:
612
622
  return None
613
623
 
614
- def create_or_update_device_session(self, user_id, ip_address, user_agent):
624
+ def create_or_update_device_session(self, user_id, ip_address, user_agent, device_info=None):
615
625
  """创建或更新设备会话"""
616
- device_fingerprint = self._generate_device_fingerprint(user_agent, ip_address)
617
- device_info = self._parse_user_agent(user_agent)
626
+ # 解析用户代理获取基本信息
627
+ parsed_ua = self._parse_user_agent(user_agent)
628
+
629
+ # 合并设备信息
630
+ full_device_info = {
631
+ 'user_agent': user_agent,
632
+ 'platform': parsed_ua.get('platform'),
633
+ **parsed_ua,
634
+ **(device_info or {})
635
+ }
636
+
637
+ device_fingerprint = self._generate_device_fingerprint(full_device_info)
618
638
  device_id = secrets.token_urlsafe(32)
619
639
 
620
640
  conn = self.pool.connection()
@@ -638,12 +658,22 @@ class StandaloneAuthManager:
638
658
 
639
659
  cursor.execute('''
640
660
  UPDATE device_sessions
641
- SET ip_address = %s, user_agent = %s, last_activity = %s,
642
- device_name = %s, device_type = %s, platform = %s, browser = %s
661
+ SET current_ip = %s, user_agent = %s, last_activity = %s,
662
+ device_name = %s, device_type = %s, platform = %s, browser = %s,
663
+ browser_version = %s, screen_resolution = %s, timezone_offset = %s,
664
+ language = %s, hardware_concurrency = %s
643
665
  WHERE id = %s
644
666
  ''', (ip_address, user_agent, current_time_utc,
645
- device_info['device_name'], device_info['device_type'],
646
- device_info['platform'], device_info['browser'], device_session_id))
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))
647
677
  else:
648
678
  # 检查设备数量限制
649
679
  cursor.execute('''
@@ -670,15 +700,24 @@ class StandaloneAuthManager:
670
700
  cursor.execute('''
671
701
  INSERT INTO device_sessions (
672
702
  user_id, device_id, device_fingerprint, device_name, device_type,
673
- platform, browser, ip_address, user_agent, last_activity
674
- ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
675
- ''', (user_id, device_id, device_fingerprint, device_info['device_name'],
676
- device_info['device_type'], device_info['platform'], device_info['browser'],
677
- ip_address, user_agent, current_time_utc))
703
+ platform, browser, browser_version, screen_resolution, timezone_offset,
704
+ language, hardware_concurrency, current_ip, first_ip, user_agent, last_activity
705
+ ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
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
717
 
679
718
  device_session_id = cursor.lastrowid
680
719
 
681
- return device_session_id, device_id, device_info['device_name']
720
+ return device_session_id, device_id, full_device_info.get('device_name', 'Unknown Device')
682
721
 
683
722
  finally:
684
723
  cursor.close()
@@ -1214,10 +1253,34 @@ class StandaloneAuthManager:
1214
1253
  WHERE id = %s
1215
1254
  ''', (device_session_id,))
1216
1255
 
1217
- def _generate_device_fingerprint(self, user_agent, ip_address):
1218
- """生成设备指纹"""
1219
- fingerprint_data = f"{user_agent}:{ip_address}:{datetime.now().strftime('%Y%m%d')}"
1220
- return hashlib.sha256(fingerprint_data.encode()).hexdigest()[:32]
1256
+ def _generate_device_fingerprint(self, device_info):
1257
+ """
1258
+ 生成稳定的设备指纹
1259
+
1260
+ Args:
1261
+ device_info (dict): 设备信息
1262
+ - user_agent: 用户代理
1263
+ - screen_resolution: 屏幕分辨率
1264
+ - timezone_offset: 时区偏移
1265
+ - language: 浏览器语言
1266
+ - hardware_concurrency: CPU核心数
1267
+ - platform: 平台信息
1268
+ """
1269
+ # 提取稳定的设备特征
1270
+ stable_features = [
1271
+ device_info.get('user_agent', ''),
1272
+ device_info.get('screen_resolution', ''),
1273
+ str(device_info.get('timezone_offset', 0)),
1274
+ device_info.get('language', ''),
1275
+ str(device_info.get('hardware_concurrency', 0)),
1276
+ device_info.get('platform', ''),
1277
+ ]
1278
+
1279
+ # 过滤空值并连接
1280
+ fingerprint_data = ':'.join(filter(None, stable_features))
1281
+
1282
+ # 生成SHA256哈希
1283
+ return hashlib.sha256(fingerprint_data.encode('utf-8')).hexdigest()[:32]
1221
1284
 
1222
1285
  def _parse_user_agent(self, user_agent):
1223
1286
  """解析User-Agent获取设备信息"""
@@ -1320,8 +1383,10 @@ class StandaloneAuthManager:
1320
1383
 
1321
1384
  try:
1322
1385
  cursor.execute('''
1323
- SELECT device_id, device_fingerprint, device_name, device_type, platform, browser,
1324
- ip_address, last_activity, created_at, is_active
1386
+ SELECT device_id, device_fingerprint, device_name, custom_name, device_type,
1387
+ platform, browser, browser_version, screen_resolution, timezone_offset,
1388
+ language, hardware_concurrency, current_ip, first_ip,
1389
+ last_activity, created_at, is_active, trust_level
1325
1390
  FROM device_sessions
1326
1391
  WHERE user_id = %s AND is_active = 1
1327
1392
  ORDER BY last_activity DESC
@@ -1336,13 +1401,24 @@ class StandaloneAuthManager:
1336
1401
  if is_current:
1337
1402
  current_device_id = device['device_id']
1338
1403
 
1404
+ # 生成显示名称(优先使用自定义名称)
1405
+ display_name = device['custom_name'] or device['device_name']
1406
+
1339
1407
  devices_list.append({
1340
1408
  'device_id': device['device_id'],
1341
- 'device_name': device['device_name'],
1409
+ 'device_name': display_name,
1410
+ 'custom_name': device['custom_name'],
1342
1411
  'device_type': device['device_type'],
1343
1412
  'platform': device['platform'],
1344
1413
  'browser': device['browser'],
1345
- 'ip_address': device['ip_address'],
1414
+ 'browser_version': device['browser_version'],
1415
+ 'screen_resolution': device['screen_resolution'],
1416
+ 'timezone_offset': device['timezone_offset'],
1417
+ 'language': device['language'],
1418
+ 'hardware_concurrency': device['hardware_concurrency'],
1419
+ 'current_ip': device['current_ip'],
1420
+ 'first_ip': device['first_ip'],
1421
+ 'trust_level': device['trust_level'],
1346
1422
  'last_activity': device['last_activity'].isoformat() if device['last_activity'] else None,
1347
1423
  'created_at': device['created_at'].isoformat() if device['created_at'] else None,
1348
1424
  'is_current': is_current
@@ -1528,6 +1604,73 @@ class StandaloneAuthManager:
1528
1604
  cursor.close()
1529
1605
  conn.close()
1530
1606
 
1607
+ def rename_device(self, user_id, device_id, custom_name):
1608
+ """重命名设备"""
1609
+ conn = self.pool.connection()
1610
+ cursor = conn.cursor()
1611
+
1612
+ try:
1613
+ # 验证设备是否属于该用户
1614
+ cursor.execute('''
1615
+ SELECT id FROM device_sessions
1616
+ WHERE user_id = %s AND device_id = %s AND is_active = 1
1617
+ ''', (user_id, device_id))
1618
+
1619
+ device = cursor.fetchone()
1620
+
1621
+ if not device:
1622
+ return {'success': False, 'message': '设备不存在或已失效'}
1623
+
1624
+ # 更新自定义名称
1625
+ cursor.execute('''
1626
+ UPDATE device_sessions
1627
+ SET custom_name = %s
1628
+ WHERE id = %s
1629
+ ''', (custom_name.strip() if custom_name else None, device['id']))
1630
+
1631
+ return {'success': True, 'message': '设备重命名成功'}
1632
+
1633
+ except Exception as e:
1634
+ return {'success': False, 'message': f'设备重命名失败: {str(e)}'}
1635
+ finally:
1636
+ cursor.close()
1637
+ conn.close()
1638
+
1639
+ def set_device_trust_level(self, user_id, device_id, trust_level):
1640
+ """设置设备信任级别"""
1641
+ if trust_level not in ['trusted', 'normal', 'suspicious']:
1642
+ return {'success': False, 'message': '无效的信任级别'}
1643
+
1644
+ conn = self.pool.connection()
1645
+ cursor = conn.cursor()
1646
+
1647
+ try:
1648
+ # 验证设备是否属于该用户
1649
+ cursor.execute('''
1650
+ SELECT id FROM device_sessions
1651
+ WHERE user_id = %s AND device_id = %s AND is_active = 1
1652
+ ''', (user_id, device_id))
1653
+
1654
+ device = cursor.fetchone()
1655
+
1656
+ if not device:
1657
+ return {'success': False, 'message': '设备不存在或已失效'}
1658
+
1659
+ # 更新信任级别
1660
+ cursor.execute('''
1661
+ UPDATE device_sessions
1662
+ SET trust_level = %s
1663
+ WHERE id = %s
1664
+ ''', (trust_level, device['id']))
1665
+
1666
+ return {'success': True, 'message': '设备信任级别更新成功'}
1667
+
1668
+ except Exception as e:
1669
+ return {'success': False, 'message': f'设备信任级别更新失败: {str(e)}'}
1670
+ finally:
1671
+ cursor.close()
1672
+ conn.close()
1673
+
1531
1674
 
1532
1675
  # Flask集成装饰器
1533
1676
  def require_auth(auth_manager):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mdbq
3
- Version: 4.0.95
3
+ Version: 4.0.96
4
4
  Home-page: https://pypi.org/project/mdbq
5
5
  Author: xigua,
6
6
  Author-email: 2587125111@qq.com
@@ -1,7 +1,7 @@
1
1
  mdbq/__init__.py,sha256=Il5Q9ATdX8yXqVxtP_nYqUhExzxPC_qk_WXQ_4h0exg,16
2
- mdbq/__version__.py,sha256=_cjJRgt5f4NvaVHrfLAZ5D43gJ6Uuxh9IhEFJwJ5Kc0,18
2
+ mdbq/__version__.py,sha256=upYmcfx0UWjE76j0j97qCRGjDZGCKhrJ15LsMJQXb8E,18
3
3
  mdbq/auth/__init__.py,sha256=pnPMAt63sh1B6kEvmutUuro46zVf2v2YDAG7q-jV_To,24
4
- mdbq/auth/auth_backend.py,sha256=54xSg9pDw5W7otm1dcvksQiJLRGDKuNRb83whUOjI_0,69933
4
+ mdbq/auth/auth_backend.py,sha256=R0qwedMEtJxbKcDUkl1ELl4lVTunFeiXsa_Xo3LHiQU,76455
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.95.dist-info/METADATA,sha256=n82sUxYXuss5vPMrmpxRv3IQtOOjGlam91iAgYv8n5w,364
37
- mdbq-4.0.95.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
38
- mdbq-4.0.95.dist-info/top_level.txt,sha256=2FQ-uLnCSB-OwFiWntzmwosW3X2Xqsg0ewh1axsaylA,5
39
- mdbq-4.0.95.dist-info/RECORD,,
36
+ mdbq-4.0.96.dist-info/METADATA,sha256=XQFXH6TeJlSgpYJN7v8h_H0x0-qUJyh36kV-X4TzOBc,364
37
+ mdbq-4.0.96.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
38
+ mdbq-4.0.96.dist-info/top_level.txt,sha256=2FQ-uLnCSB-OwFiWntzmwosW3X2Xqsg0ewh1axsaylA,5
39
+ mdbq-4.0.96.dist-info/RECORD,,
File without changes