diff --git a/src/core/settings.py b/src/core/settings.py index 824f89b..19b0cd2 100644 --- a/src/core/settings.py +++ b/src/core/settings.py @@ -45,9 +45,11 @@ class RefreshToken(BaseModel): class Settings(BaseSettings): api: ApiPrefix = ApiPrefix() db: DbSettings = DbSettings() - access_token: AccessToken + access_token: AccessToken refresh_token: RefreshToken - model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", env_nested_delimiter='__') + model_config = SettingsConfigDict( + env_file=".env", env_file_encoding="utf-8", env_nested_delimiter="__" + ) settings = Settings() # type: ignore diff --git a/src/migrations/versions/2025_09_21_2016-5821f37941a8_expire_and_fingerprintjs_token.py b/src/migrations/versions/2025_09_21_2016-5821f37941a8_expire_and_fingerprintjs_token.py index a221cc7..5d43667 100644 --- a/src/migrations/versions/2025_09_21_2016-5821f37941a8_expire_and_fingerprintjs_token.py +++ b/src/migrations/versions/2025_09_21_2016-5821f37941a8_expire_and_fingerprintjs_token.py @@ -5,14 +5,15 @@ Revises: b879d3502c37 Create Date: 2025-09-21 20:16:48.289050 """ + from typing import Sequence, Union import sqlalchemy as sa from alembic import op # revision identifiers, used by Alembic. -revision: str = '5821f37941a8' -down_revision: Union[str, None] = 'b879d3502c37' +revision: str = "5821f37941a8" +down_revision: Union[str, None] = "b879d3502c37" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None @@ -20,14 +21,25 @@ depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: """Upgrade schema.""" # ### commands auto generated by Alembic - please adjust! ### - op.add_column('refresh_tokens', sa.Column('fingerprint', sa.String(length=255), nullable=False)) - op.add_column('refresh_tokens', sa.Column('expired_at', sa.TIMESTAMP(timezone=True), server_default=sa.text("(datetime('now', '+7 days'))"), nullable=False)) + op.add_column( + "refresh_tokens", + sa.Column("fingerprint", sa.String(length=255), nullable=False), + ) + op.add_column( + "refresh_tokens", + sa.Column( + "expired_at", + sa.TIMESTAMP(timezone=True), + server_default=sa.text("(datetime('now', '+7 days'))"), + nullable=False, + ), + ) # ### end Alembic commands ### def downgrade() -> None: """Downgrade schema.""" # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('refresh_tokens', 'expired_at') - op.drop_column('refresh_tokens', 'fingerprint') + op.drop_column("refresh_tokens", "expired_at") + op.drop_column("refresh_tokens", "fingerprint") # ### end Alembic commands ### diff --git a/src/models/tokens.py b/src/models/tokens.py index 0c52fa2..601ad6f 100644 --- a/src/models/tokens.py +++ b/src/models/tokens.py @@ -15,5 +15,7 @@ class RefreshTokensORM(Base): fingerprint: Mapped[str] = mapped_column(String(255), nullable=False) expired_at: Mapped[datetime] = mapped_column( TIMESTAMP(timezone=True), - server_default=text(f"datetime('now', '+{settings.refresh_token.expire_days} days')"), + server_default=text( + f"datetime('now', '+{settings.refresh_token.expire_days} days')" + ), ) diff --git a/src/services/auth.py b/src/services/auth.py index 572fe39..a4c7705 100644 --- a/src/services/auth.py +++ b/src/services/auth.py @@ -23,9 +23,9 @@ class AuthService(BaseService): access_token = AuthManager.create_access_token(user_data.model_dump()) refresh_token = AuthManager.create_refresh_token() await self.session.auth.create_one({ - "token": refresh_token, + "token": refresh_token, "user_id": user_data.id, - "fingerprint": fingerprint + "fingerprint": fingerprint, }) return { "access_token": access_token, @@ -34,28 +34,27 @@ class AuthService(BaseService): } async def login(self, username: str, password: str, fingerprint: str) -> dict: + login_exception = HTTPException( + status_code=401, + detail="Incorrect username or password", + ) result = await self.session.user.get_one_or_none(username=username) if result is None: - raise HTTPException( - status_code=401, - detail="Incorrect username or password", - ) + raise login_exception user = UserWithHashedPass.model_validate(result) token_data = TokenData.model_validate(user.model_dump()) verify = AuthManager.verify_password( plain_password=password, hashed_password=user.hashed_password ) if not verify or user.is_active is False: - raise HTTPException( - status_code=401, - detail="Incorrect username or password", - ) + raise login_exception tokens = await self._tokens_create(token_data, fingerprint) await self.session.commit() - print(tokens) return tokens - async def refresh_tokens(self, refresh_token: str, user_data: TokenData, fingerprint: str) -> dict: + async def refresh_tokens( + self, refresh_token: str, user_data: TokenData, fingerprint: str + ) -> dict: token_record = await self.session.auth.get_one_or_none(token=refresh_token) if not token_record or token_record.user_id != user_data.id: raise HTTPException(status_code=401, detail="Invalid refresh token") @@ -63,7 +62,7 @@ class AuthService(BaseService): await self.session.auth.delete_one(token=refresh_token) await self.session.commit() return token - + async def delete_token(self, token: str) -> None: await self.session.auth.delete_one(token=token) await self.session.commit() diff --git a/tests/integration_tests/test_auth_api.py b/tests/integration_tests/test_auth_api.py index 28d59c8..ac66d89 100644 --- a/tests/integration_tests/test_auth_api.py +++ b/tests/integration_tests/test_auth_api.py @@ -1,5 +1,6 @@ -from httpx import AsyncClient import pytest +from httpx import AsyncClient + from src.core.settings import settings from src.schemas.users import User