FastAPI + Celery + Next.js + Postgres/Redis app with company monitoring, source collection, LLM-based change analysis, enrichment, and account security (Turnstile, escalating lockout, email verification).
56 lines
1.9 KiB
Python
56 lines
1.9 KiB
Python
from __future__ import annotations
|
|
|
|
import uuid
|
|
from datetime import UTC, datetime
|
|
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.db.base import ensure_aware_utc
|
|
from app.models.refresh_token import RefreshToken
|
|
|
|
|
|
class RefreshTokenRepository:
|
|
def __init__(self, db: AsyncSession) -> None:
|
|
self.db = db
|
|
|
|
async def create(
|
|
self, *, user_id: uuid.UUID, token_hash: str, expires_at: datetime
|
|
) -> RefreshToken:
|
|
record = RefreshToken(user_id=user_id, token_hash=token_hash, expires_at=expires_at)
|
|
self.db.add(record)
|
|
await self.db.flush()
|
|
return record
|
|
|
|
async def get_valid_by_hash(self, token_hash: str) -> RefreshToken | None:
|
|
result = await self.db.execute(
|
|
select(RefreshToken).where(RefreshToken.token_hash == token_hash)
|
|
)
|
|
record = result.scalar_one_or_none()
|
|
if record is None:
|
|
return None
|
|
if record.revoked_at is not None:
|
|
return None
|
|
if ensure_aware_utc(record.expires_at) < datetime.now(UTC):
|
|
return None
|
|
return record
|
|
|
|
async def revoke(self, record: RefreshToken) -> None:
|
|
record.revoked_at = datetime.now(UTC)
|
|
await self.db.flush()
|
|
|
|
async def revoke_all_for_user(self, user_id: uuid.UUID) -> None:
|
|
"""Invalidates every active session for this user - used after a
|
|
password reset, since an attacker who had a valid refresh token
|
|
shouldn't stay logged in past the password change that locked them
|
|
out going forward."""
|
|
result = await self.db.execute(
|
|
select(RefreshToken).where(
|
|
RefreshToken.user_id == user_id, RefreshToken.revoked_at.is_(None)
|
|
)
|
|
)
|
|
now = datetime.now(UTC)
|
|
for record in result.scalars().all():
|
|
record.revoked_at = now
|
|
await self.db.flush()
|