auth_service.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. import uuid, bcrypt, random, string
  2. from sqlalchemy.orm import Session
  3. from datetime import datetime, timedelta
  4. from redis.asyncio import Redis
  5. from app.utils.redis_utils import redis_qpush
  6. from app.core.auth import get_current_user
  7. from app.core.biz_exception import NotFoundError, PermissionDeniedError, BizLogicError
  8. from app.models.user import VasUser
  9. from app.models.session import VasSession
  10. from app.models.email_verification import VasEmailVerification
  11. from app.schemas.auth import AutoRegisterRequest, BindEmailRequest, LoginRequest
  12. def _random_password(length=16):
  13. return ''.join(random.choices(string.ascii_letters + string.digits + "!@#$%", k=length))
  14. class AuthService:
  15. # -----------------------
  16. # 自动注册
  17. # -----------------------
  18. @staticmethod
  19. def auto_register(db: Session, req:AutoRegisterRequest):
  20. uid = str(uuid.uuid4())
  21. user = VasUser(
  22. id=uid,
  23. role="user",
  24. nickname="anonymous visitor",
  25. preferred_language="en",
  26. timezone="Asia/Shanghai",
  27. register_ip=req.register_ip,
  28. )
  29. db.add(user)
  30. db.commit()
  31. # 创建 session
  32. token = f"tok_{uuid.uuid4().hex}"
  33. session = VasSession(
  34. id=token,
  35. user_id=uid,
  36. user_agent=req.user_agent or "",
  37. ip=req.register_ip,
  38. expire_at=datetime.utcnow() + timedelta(days=7)
  39. )
  40. db.add(session)
  41. db.commit()
  42. return {
  43. "user": user,
  44. "token": token
  45. }
  46. # -----------------------
  47. # 绑定邮箱
  48. # -----------------------
  49. @staticmethod
  50. def bind_email(db: Session, req: BindEmailRequest, auth_user: VasUser, redis_client:Redis):
  51. user = db.query(VasUser).filter_by(email=req.email).first()
  52. if user:
  53. raise BizLogicError("Email has been bound")
  54. token = uuid.uuid4().hex
  55. record = VasEmailVerification(
  56. user_id=auth_user.id,
  57. email=req.email,
  58. token=token,
  59. expire_at=datetime.utcnow() + timedelta(minutes=30)
  60. )
  61. db.add(record)
  62. db.commit()
  63. print(f"📧 send verification email token={token}")
  64. notification_payload = {
  65. "notification_id": uuid.uuid4().hex,
  66. "type": "verification email",
  67. "user_id": auth_user.id,
  68. "channels": ["email"],
  69. "template_id": "verification_email",
  70. "payload": {
  71. "sendTo": req.email,
  72. "token": token
  73. }
  74. }
  75. redis_qpush(redis_client, 'vas_notification_queue', notification_payload)
  76. return True
  77. # -----------------------
  78. # 邮箱验证
  79. # -----------------------
  80. @staticmethod
  81. def verify_email(db: Session, token: str, redis_client:Redis):
  82. record = (
  83. db.query(VasEmailVerification)
  84. .filter_by(token=token, used=0)
  85. .first()
  86. )
  87. if not record:
  88. raise BizLogicError("Token invalid")
  89. if record.expire_at < datetime.utcnow():
  90. raise BizLogicError("Token expired")
  91. # 更新 user.email
  92. user = db.query(VasUser).filter_by(id=record.user_id).first()
  93. user.email = record.email
  94. # 随机密码
  95. plain = _random_password()
  96. hashed = bcrypt.hashpw(plain.encode(), bcrypt.gensalt()).decode()
  97. setattr(user, "password_hash", hashed)
  98. record.used = 1
  99. db.commit()
  100. print(f"📧 send login email and password")
  101. notification_payload = {
  102. "notification_id": uuid.uuid4().hex,
  103. "type": "login credentials",
  104. "user_id": record.user_id,
  105. "channels": ["email"],
  106. "template_id": "login_credentials",
  107. "payload": {
  108. "sendTo": record.email,
  109. "username": record.email,
  110. "password": plain
  111. }
  112. }
  113. redis_qpush(redis_client, 'vas_notification_queue', notification_payload)
  114. return True
  115. # -----------------------
  116. # 用户登录
  117. # -----------------------
  118. @staticmethod
  119. def login(db: Session, req:LoginRequest):
  120. user = db.query(VasUser).filter_by(email=req.email).first()
  121. if not user:
  122. raise NotFoundError("User not found")
  123. # 对比密码
  124. if not bcrypt.checkpw(req.password.encode(), user.password_hash.encode()):
  125. raise PermissionDeniedError("Password incorrect")
  126. # 创建 session
  127. token = "tok_" + uuid.uuid4().hex
  128. session = VasSession(
  129. id=token,
  130. user_id=user.id,
  131. user_agent="",
  132. ip="",
  133. expire_at=datetime.utcnow() + timedelta(days=7)
  134. )
  135. db.add(session)
  136. db.commit()
  137. return {
  138. "user": user,
  139. "token": token
  140. }