from __future__ import annotations from datetime import UTC, datetime from sqlalchemy import delete, select from app.core.database import session_scope from app.modules.auth.models import EmailVerificationToken, PasswordResetToken, RefreshToken def _detach(db, instance): db.refresh(instance) db.expunge(instance) return instance def create_refresh_token( user_id: str, token_hash: str, family_id: str, expires_at: datetime, ) -> RefreshToken: with session_scope() as db: token = RefreshToken( user_id=user_id, token_hash=token_hash, family_id=family_id, expires_at=expires_at, ) db.add(token) db.flush() return _detach(db, token) def get_refresh_token(token_hash: str) -> RefreshToken | None: with session_scope() as db: token = db.scalar(select(RefreshToken).where(RefreshToken.token_hash == token_hash)) if not token: return None return _detach(db, token) def revoke_refresh_token(token_hash: str) -> RefreshToken | None: with session_scope() as db: token = db.scalar(select(RefreshToken).where(RefreshToken.token_hash == token_hash)) if not token: return None token.revoked_at = datetime.now(UTC) db.flush() return _detach(db, token) def revoke_user_families(user_id: str) -> None: with session_scope() as db: tokens = db.scalars( select(RefreshToken).where( RefreshToken.user_id == user_id, RefreshToken.revoked_at.is_(None), ) ).all() now = datetime.now(UTC) for token in tokens: token.revoked_at = now def revoke_family_tokens(family_id: str) -> None: with session_scope() as db: tokens = db.scalars( select(RefreshToken).where( RefreshToken.family_id == family_id, RefreshToken.revoked_at.is_(None), ) ).all() now = datetime.now(UTC) for token in tokens: token.revoked_at = now def create_email_verification_token(user_id: str, token_hash: str, expires_at: datetime) -> None: with session_scope() as db: db.execute( delete(EmailVerificationToken).where( EmailVerificationToken.user_id == user_id, EmailVerificationToken.used_at.is_(None), ) ) db.add(EmailVerificationToken(user_id=user_id, token_hash=token_hash, expires_at=expires_at)) def get_email_verification_token(token_hash: str) -> EmailVerificationToken | None: with session_scope() as db: token = db.scalar( select(EmailVerificationToken).where(EmailVerificationToken.token_hash == token_hash) ) if not token: return None return _detach(db, token) def mark_email_verification_token_used(token_hash: str) -> None: with session_scope() as db: token = db.scalar( select(EmailVerificationToken).where(EmailVerificationToken.token_hash == token_hash) ) if token: token.used_at = datetime.now(UTC) def create_password_reset_token(user_id: str, token_hash: str, expires_at: datetime) -> None: with session_scope() as db: db.execute( delete(PasswordResetToken).where( PasswordResetToken.user_id == user_id, PasswordResetToken.used_at.is_(None), ) ) db.add(PasswordResetToken(user_id=user_id, token_hash=token_hash, expires_at=expires_at)) def get_password_reset_token(token_hash: str) -> PasswordResetToken | None: with session_scope() as db: token = db.scalar(select(PasswordResetToken).where(PasswordResetToken.token_hash == token_hash)) if not token: return None return _detach(db, token) def mark_password_reset_token_used(token_hash: str) -> None: with session_scope() as db: token = db.scalar(select(PasswordResetToken).where(PasswordResetToken.token_hash == token_hash)) if token: token.used_at = datetime.now(UTC)