Files
dy/backend/auth/password_reset.py
2026-07-23 17:56:25 +08:00

65 lines
1.6 KiB
Python

"""密码重置令牌创建与校验。"""
from __future__ import annotations
import secrets
from datetime import datetime, timedelta
from sqlalchemy import delete, select
from sqlalchemy.ext.asyncio import AsyncSession
from models.models import PasswordResetToken, User
def _token_expires_at(hours: int) -> datetime:
return datetime.utcnow() + timedelta(hours=max(1, min(168, int(hours or 24))))
async def invalidate_user_reset_tokens(db: AsyncSession, user_id: int) -> None:
await db.execute(
delete(PasswordResetToken).where(PasswordResetToken.user_id == user_id)
)
async def create_password_reset_token(
db: AsyncSession,
user: User,
expire_hours: int = 24,
) -> str:
await invalidate_user_reset_tokens(db, user.id)
token = secrets.token_urlsafe(32)
db.add(
PasswordResetToken(
user_id=user.id,
token=token,
expires_at=_token_expires_at(expire_hours),
)
)
await db.flush()
return token
async def verify_password_reset_token(db: AsyncSession, token: str) -> User | None:
value = (token or "").strip()
if not value:
return None
result = await db.execute(
select(PasswordResetToken, User)
.join(User, User.id == PasswordResetToken.user_id)
.where(PasswordResetToken.token == value)
)
row = result.first()
if not row:
return None
record, user = row
if record.expires_at < datetime.utcnow():
await db.delete(record)
await db.flush()
return None
await db.delete(record)
await db.flush()
return user