62 lines
1.8 KiB
Python
62 lines
1.8 KiB
Python
"""Refresh-token repository — adapter implementing
|
|
``app.domain.ports.RefreshTokenRepository``.
|
|
"""
|
|
|
|
import datetime as dt
|
|
import uuid
|
|
|
|
from sqlalchemy import select, update
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.infrastructure.db.models import RefreshTokenModel
|
|
|
|
|
|
class SqlAlchemyRefreshTokenRepository:
|
|
def __init__(self, session: AsyncSession) -> None:
|
|
self._session = session
|
|
|
|
async def add(
|
|
self,
|
|
*,
|
|
jti: uuid.UUID,
|
|
user_id: uuid.UUID,
|
|
token_hash: str,
|
|
expires_at: dt.datetime,
|
|
) -> None:
|
|
self._session.add(
|
|
RefreshTokenModel(
|
|
jti=jti,
|
|
user_id=user_id,
|
|
token_hash=token_hash,
|
|
expires_at=expires_at,
|
|
)
|
|
)
|
|
await self._session.flush()
|
|
|
|
async def is_valid(self, jti: uuid.UUID) -> bool:
|
|
row = (
|
|
await self._session.execute(
|
|
select(RefreshTokenModel).where(RefreshTokenModel.jti == jti)
|
|
)
|
|
).scalar_one_or_none()
|
|
if row is None or row.revoked_at is not None:
|
|
return False
|
|
return row.expires_at > dt.datetime.now(dt.UTC)
|
|
|
|
async def revoke(self, jti: uuid.UUID) -> None:
|
|
await self._session.execute(
|
|
update(RefreshTokenModel)
|
|
.where(RefreshTokenModel.jti == jti, RefreshTokenModel.revoked_at.is_(None))
|
|
.values(revoked_at=dt.datetime.now(dt.UTC))
|
|
)
|
|
|
|
async def revoke_all_for_user(self, user_id: uuid.UUID) -> None:
|
|
await self._session.execute(
|
|
update(RefreshTokenModel)
|
|
.where(
|
|
RefreshTokenModel.user_id == user_id,
|
|
RefreshTokenModel.revoked_at.is_(None),
|
|
)
|
|
.values(revoked_at=dt.datetime.now(dt.UTC))
|
|
)
|