auth.py 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. # -*- coding: utf-8 -*-
  2. """账号与权限:密码哈希、会话、角色权限表
  3. 只依赖标准库。密码用 PBKDF2-HMAC-SHA256,每个账号独立盐值。
  4. """
  5. import hashlib
  6. import hmac
  7. import secrets
  8. ITERATIONS = 120_000
  9. # ---------------------------------------------------------------- 角色定义
  10. # 三级角色,权限逐级递增
  11. ROLES = {
  12. "viewer": {"label": "查看", "desc": "只看数据,不能改任何东西", "level": 1},
  13. "editor": {"label": "运营", "desc": "可导入、同步、导出,不能改判定规则", "level": 2},
  14. "admin": {"label": "管理员", "desc": "全部权限,含账号管理", "level": 3},
  15. }
  16. ROLE_ORDER = ["admin", "editor", "viewer"]
  17. # 权限点
  18. PERMS = {
  19. "view": "查看数据",
  20. "export": "导出原始文件",
  21. "import": "导入数据",
  22. "sync": "同步生意经数据",
  23. "config": "修改指标阈值 / 文案",
  24. "users": "账号管理",
  25. }
  26. # 角色 → 拥有的权限
  27. GRANTS = {
  28. "viewer": {"view"},
  29. "editor": {"view", "export", "import", "sync"},
  30. "admin": {"view", "export", "import", "sync", "config", "users"},
  31. }
  32. def can(role, perm):
  33. return perm in GRANTS.get(role or "", set())
  34. def role_label(role):
  35. return ROLES.get(role or "", {}).get("label", role or "-")
  36. # ---------------------------------------------------------------- 密码
  37. def hash_password(password, salt=None):
  38. """返回 (hash_hex, salt_hex)"""
  39. salt = salt or secrets.token_hex(16)
  40. digest = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"),
  41. salt.encode("utf-8"), ITERATIONS)
  42. return digest.hex(), salt
  43. def verify_password(password, stored_hash, salt):
  44. if not stored_hash or not salt:
  45. return False
  46. digest, _ = hash_password(password, salt)
  47. return hmac.compare_digest(digest, stored_hash)
  48. def check_strength(password):
  49. """返回 (是否合格, 提示)"""
  50. if not password or len(password) < 6:
  51. return False, "密码至少 6 位"
  52. if password.isdigit():
  53. return False, "密码不能全是数字"
  54. return True, ""
  55. # ---------------------------------------------------------------- 会话
  56. def new_token():
  57. return secrets.token_urlsafe(32)
  58. def hash_token(token):
  59. """会话令牌在库里只存哈希,泄露数据库也拿不到可用的 cookie"""
  60. return hashlib.sha256(token.encode("utf-8")).hexdigest()