security: harden proxy IP trust, token flow, health and sessions
This commit is contained in:
103
email_service.py
103
email_service.py
@@ -1370,20 +1370,18 @@ def generate_email_token(email: str, token_type: str, user_id: int = None) -> st
|
||||
return token
|
||||
|
||||
|
||||
def verify_email_token(token: str, token_type: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
验证Token
|
||||
|
||||
Returns:
|
||||
成功返回 {'user_id': int, 'email': str},失败返回 None
|
||||
"""
|
||||
def _get_email_token_payload(token: str, token_type: str) -> Optional[Dict[str, Any]]:
|
||||
"""获取并校验邮件Token(不消费)。"""
|
||||
with db_pool.get_db() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT id, user_id, email, expires_at, used
|
||||
FROM email_tokens
|
||||
WHERE token = ? AND token_type = ?
|
||||
""", (token, token_type))
|
||||
""",
|
||||
(token, token_type),
|
||||
)
|
||||
|
||||
row = cursor.fetchone()
|
||||
if not row:
|
||||
@@ -1391,21 +1389,55 @@ def verify_email_token(token: str, token_type: str) -> Optional[Dict[str, Any]]:
|
||||
|
||||
token_id, user_id, email, expires_at, used = row
|
||||
|
||||
# 检查是否已使用
|
||||
if used:
|
||||
return None
|
||||
|
||||
# 检查是否过期
|
||||
if parse_datetime(expires_at) < datetime.now(BEIJING_TZ):
|
||||
return None
|
||||
|
||||
# 标记为已使用
|
||||
cursor.execute("""
|
||||
UPDATE email_tokens SET used = 1 WHERE id = ?
|
||||
""", (token_id,))
|
||||
conn.commit()
|
||||
return {'token_id': token_id, 'user_id': user_id, 'email': email}
|
||||
|
||||
return {'user_id': user_id, 'email': email}
|
||||
|
||||
def consume_email_token(token_id: int) -> bool:
|
||||
"""将邮件Token标记为已使用。"""
|
||||
with db_pool.get_db() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
UPDATE email_tokens
|
||||
SET used = 1
|
||||
WHERE id = ? AND used = 0
|
||||
""",
|
||||
(int(token_id),),
|
||||
)
|
||||
conn.commit()
|
||||
return cursor.rowcount > 0
|
||||
|
||||
|
||||
def verify_email_token(token: str, token_type: str, *, consume: bool = True) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
验证Token
|
||||
|
||||
Args:
|
||||
token: token字符串
|
||||
token_type: token类型
|
||||
consume: 是否在验证成功后立刻消费(默认True)
|
||||
|
||||
Returns:
|
||||
consume=True: {'user_id': int, 'email': str}
|
||||
consume=False: {'token_id': int, 'user_id': int, 'email': str}
|
||||
失败返回 None
|
||||
"""
|
||||
payload = _get_email_token_payload(token, token_type)
|
||||
if not payload:
|
||||
return None
|
||||
|
||||
if consume:
|
||||
if not consume_email_token(payload['token_id']):
|
||||
return None
|
||||
return {'user_id': payload['user_id'], 'email': payload['email']}
|
||||
|
||||
return payload
|
||||
|
||||
|
||||
def check_rate_limit(email: str, token_type: str) -> bool:
|
||||
@@ -1600,29 +1632,7 @@ def verify_password_reset_token(token: str) -> Optional[Dict[str, Any]]:
|
||||
Returns:
|
||||
成功返回 {'user_id': int, 'email': str},失败返回 None
|
||||
"""
|
||||
with db_pool.get_db() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
SELECT id, user_id, email, expires_at, used
|
||||
FROM email_tokens
|
||||
WHERE token = ? AND token_type = ?
|
||||
""", (token, EMAIL_TYPE_RESET))
|
||||
|
||||
row = cursor.fetchone()
|
||||
if not row:
|
||||
return None
|
||||
|
||||
token_id, user_id, email, expires_at, used = row
|
||||
|
||||
# 检查是否已使用
|
||||
if used:
|
||||
return None
|
||||
|
||||
# 检查是否过期
|
||||
if parse_datetime(expires_at) < datetime.now(BEIJING_TZ):
|
||||
return None
|
||||
|
||||
return {'user_id': user_id, 'email': email, 'token_id': token_id}
|
||||
return verify_email_token(token, EMAIL_TYPE_RESET, consume=False)
|
||||
|
||||
|
||||
def confirm_password_reset(token: str) -> Optional[Dict[str, Any]]:
|
||||
@@ -1636,13 +1646,8 @@ def confirm_password_reset(token: str) -> Optional[Dict[str, Any]]:
|
||||
if not result:
|
||||
return None
|
||||
|
||||
# 标记为已使用
|
||||
with db_pool.get_db() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
UPDATE email_tokens SET used = 1 WHERE id = ?
|
||||
""", (result['token_id'],))
|
||||
conn.commit()
|
||||
if not consume_email_token(result['token_id']):
|
||||
return None
|
||||
|
||||
return {'user_id': result['user_id'], 'email': result['email']}
|
||||
|
||||
@@ -1706,14 +1711,14 @@ def send_bind_email_verification(
|
||||
)
|
||||
|
||||
|
||||
def verify_bind_email_token(token: str) -> Optional[Dict[str, Any]]:
|
||||
def verify_bind_email_token(token: str, *, consume: bool = True) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
验证邮箱绑定Token
|
||||
|
||||
Returns:
|
||||
成功返回 {'user_id': int, 'email': str},失败返回 None
|
||||
"""
|
||||
return verify_email_token(token, EMAIL_TYPE_BIND)
|
||||
return verify_email_token(token, EMAIL_TYPE_BIND, consume=consume)
|
||||
|
||||
|
||||
def send_security_alert_email(
|
||||
|
||||
Reference in New Issue
Block a user