Skip to content

Commit 49c296d

Browse files
committed
rewrite to new design
Signed-off-by: rafsaf <rafal.safin12@gmail.com>
1 parent bdfa9d7 commit 49c296d

42 files changed

Lines changed: 453 additions & 362 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.pre-commit-config.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
repos:
22
- repo: https://github.com/pre-commit/pre-commit-hooks
3-
rev: v5.0.0
3+
rev: v6.0.0
44
hooks:
55
- id: check-yaml
66

77
- repo: https://github.com/astral-sh/ruff-pre-commit
8-
rev: v0.11.12
8+
rev: v0.14.14
99
hooks:
1010
- id: ruff-format
1111

1212
- repo: https://github.com/astral-sh/ruff-pre-commit
13-
rev: v0.11.12
13+
rev: v0.14.14
1414
hooks:
15-
- id: ruff
15+
- id: ruff-check
1616
args: [--fix]

alembic/env.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,12 @@
1919
# for 'autogenerate' support
2020
# from myapp import mymodel
2121
# target_metadata = mymodel.Base.metadata
22-
from app.models import Base # noqa
22+
from app.core.models import Base # noqa
2323

2424
target_metadata = Base.metadata
2525

26-
# other values from the config, defined by the needs of env.py,
27-
# can be acquired:
28-
# my_important_option = config.get_main_option("my_important_option")
29-
# ... etc.
26+
# import other models here
27+
import app.auth.models # noqa
3028

3129

3230
def get_database_uri() -> str:
@@ -93,4 +91,16 @@ async def run_migrations_online() -> None:
9391
if context.is_offline_mode():
9492
run_migrations_offline()
9593
else:
96-
asyncio.run(run_migrations_online())
94+
try:
95+
loop: asyncio.AbstractEventLoop | None = asyncio.get_running_loop()
96+
except RuntimeError:
97+
loop = None
98+
99+
if loop and loop.is_running():
100+
# pytest-asyncio or other test runner is running the event loop
101+
# so we need to use run_coroutine_threadsafe
102+
future = asyncio.run_coroutine_threadsafe(run_migrations_online(), loop)
103+
future.result(timeout=15)
104+
else:
105+
# no event loop is running, safe to use asyncio.run
106+
asyncio.run(run_migrations_online())

alembic/versions/20250602_2142_initial_migration_be24780c0da0.py renamed to alembic/versions/20260203_1616_initial_auth_683275eeb305.py

Lines changed: 22 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
"""initial_migration
1+
"""initial_auth
22
3-
Revision ID: be24780c0da0
3+
Revision ID: 683275eeb305
44
Revises:
5-
Create Date: 2025-06-02 21:42:16.031375
5+
Create Date: 2026-02-03 16:16:09.776977
66
77
"""
88

@@ -11,73 +11,59 @@
1111
from alembic import op
1212

1313
# revision identifiers, used by Alembic.
14-
revision = "be24780c0da0"
14+
revision = "683275eeb305"
1515
down_revision = None
1616
branch_labels = None
1717
depends_on = None
1818

1919

20-
def upgrade() -> None:
20+
def upgrade():
2121
# ### commands auto generated by Alembic - please adjust! ###
2222
op.create_table(
23-
"user_account",
24-
sa.Column("user_id", sa.Uuid(as_uuid=False), nullable=False),
23+
"auth_user",
24+
sa.Column("user_id", sa.String(length=36), nullable=False),
2525
sa.Column("email", sa.String(length=256), nullable=False),
2626
sa.Column("hashed_password", sa.String(length=128), nullable=False),
2727
sa.Column(
28-
"create_time",
28+
"created_at",
2929
sa.DateTime(timezone=True),
3030
server_default=sa.text("now()"),
3131
nullable=False,
3232
),
3333
sa.Column(
34-
"update_time",
34+
"updated_at",
3535
sa.DateTime(timezone=True),
3636
server_default=sa.text("now()"),
3737
nullable=False,
3838
),
3939
sa.PrimaryKeyConstraint("user_id"),
4040
)
41-
op.create_index(
42-
op.f("ix_user_account_email"), "user_account", ["email"], unique=True
43-
)
41+
op.create_index(op.f("ix_auth_user_email"), "auth_user", ["email"], unique=True)
4442
op.create_table(
45-
"refresh_token",
43+
"auth_refresh_token",
4644
sa.Column("id", sa.BigInteger(), nullable=False),
4745
sa.Column("refresh_token", sa.String(length=512), nullable=False),
4846
sa.Column("used", sa.Boolean(), nullable=False),
4947
sa.Column("exp", sa.BigInteger(), nullable=False),
50-
sa.Column("user_id", sa.Uuid(as_uuid=False), nullable=False),
51-
sa.Column(
52-
"create_time",
53-
sa.DateTime(timezone=True),
54-
server_default=sa.text("now()"),
55-
nullable=False,
56-
),
57-
sa.Column(
58-
"update_time",
59-
sa.DateTime(timezone=True),
60-
server_default=sa.text("now()"),
61-
nullable=False,
62-
),
63-
sa.ForeignKeyConstraint(
64-
["user_id"], ["user_account.user_id"], ondelete="CASCADE"
65-
),
48+
sa.Column("user_id", sa.String(length=36), nullable=False),
49+
sa.ForeignKeyConstraint(["user_id"], ["auth_user.user_id"], ondelete="CASCADE"),
6650
sa.PrimaryKeyConstraint("id"),
6751
)
6852
op.create_index(
69-
op.f("ix_refresh_token_refresh_token"),
70-
"refresh_token",
53+
op.f("ix_auth_refresh_token_refresh_token"),
54+
"auth_refresh_token",
7155
["refresh_token"],
7256
unique=True,
7357
)
7458
# ### end Alembic commands ###
7559

7660

77-
def downgrade() -> None:
61+
def downgrade():
7862
# ### commands auto generated by Alembic - please adjust! ###
79-
op.drop_index(op.f("ix_refresh_token_refresh_token"), table_name="refresh_token")
80-
op.drop_table("refresh_token")
81-
op.drop_index(op.f("ix_user_account_email"), table_name="user_account")
82-
op.drop_table("user_account")
63+
op.drop_index(
64+
op.f("ix_auth_refresh_token_refresh_token"), table_name="auth_refresh_token"
65+
)
66+
op.drop_table("auth_refresh_token")
67+
op.drop_index(op.f("ix_auth_user_email"), table_name="auth_user")
68+
op.drop_table("auth_user")
8369
# ### end Alembic commands ###

app/api/api_messages.py

Lines changed: 0 additions & 6 deletions
This file was deleted.

app/api/api_router.py

Lines changed: 0 additions & 34 deletions
This file was deleted.

app/api/endpoints/__init__.py

Whitespace-only changes.

app/api/endpoints/users.py

Lines changed: 0 additions & 46 deletions
This file was deleted.

app/auth/api_messages.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
from typing import Any
2+
3+
JWT_ERROR_USER_REMOVED = "User removed"
4+
PASSWORD_INVALID = "Incorrect email or password"
5+
REFRESH_TOKEN_NOT_FOUND = "Refresh token not found"
6+
REFRESH_TOKEN_EXPIRED = "Refresh token expired"
7+
REFRESH_TOKEN_ALREADY_USED = "Refresh token already used"
8+
EMAIL_ADDRESS_ALREADY_USED = "Cannot use this email address"
9+
10+
11+
UNAUTHORIZED_RESPONSES: dict[int | str, dict[str, Any]] = {
12+
401: {
13+
"description": "No `Authorization` access token header, token is invalid or user removed",
14+
"content": {
15+
"application/json": {
16+
"examples": {
17+
"not authenticated": {
18+
"summary": "No authorization token header",
19+
"value": {"detail": "Not authenticated"},
20+
},
21+
"invalid token": {
22+
"summary": "Token validation failed, decode failed, it may be expired or malformed",
23+
"value": {"detail": "Token invalid: {detailed error msg}"},
24+
},
25+
"removed user": {
26+
"summary": JWT_ERROR_USER_REMOVED,
27+
"value": {"detail": JWT_ERROR_USER_REMOVED},
28+
},
29+
}
30+
}
31+
},
32+
}
33+
}
34+
35+
ACCESS_TOKEN_RESPONSES: dict[int | str, dict[str, Any]] = {
36+
400: {
37+
"description": "Invalid email or password",
38+
"content": {"application/json": {"example": {"detail": PASSWORD_INVALID}}},
39+
},
40+
}
41+
42+
REFRESH_TOKEN_RESPONSES: dict[int | str, dict[str, Any]] = {
43+
400: {
44+
"description": "Refresh token expired or is already used",
45+
"content": {
46+
"application/json": {
47+
"examples": {
48+
"refresh token expired": {
49+
"summary": REFRESH_TOKEN_EXPIRED,
50+
"value": {"detail": REFRESH_TOKEN_EXPIRED},
51+
},
52+
"refresh token already used": {
53+
"summary": REFRESH_TOKEN_ALREADY_USED,
54+
"value": {"detail": REFRESH_TOKEN_ALREADY_USED},
55+
},
56+
}
57+
}
58+
},
59+
},
60+
404: {
61+
"description": "Refresh token does not exist",
62+
"content": {
63+
"application/json": {"example": {"detail": REFRESH_TOKEN_NOT_FOUND}}
64+
},
65+
},
66+
}
Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,21 @@
1-
from collections.abc import AsyncGenerator
21
from typing import Annotated
32

43
from fastapi import Depends, HTTPException, status
54
from fastapi.security import OAuth2PasswordBearer
65
from sqlalchemy import select
76
from sqlalchemy.ext.asyncio import AsyncSession
87

9-
from app.api import api_messages
8+
from app.auth import api_messages
9+
from app.auth.jwt import verify_jwt_token
10+
from app.auth.models import User
1011
from app.core import database_session
11-
from app.core.security.jwt import verify_jwt_token
12-
from app.models import User
1312

1413
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="auth/access-token")
1514

1615

17-
async def get_session() -> AsyncGenerator[AsyncSession]:
18-
async with database_session.get_async_session() as session:
19-
yield session
20-
21-
2216
async def get_current_user(
2317
token: Annotated[str, Depends(oauth2_scheme)],
24-
session: AsyncSession = Depends(get_session),
18+
session: AsyncSession = Depends(database_session.new_async_session),
2519
) -> User:
2620
token_payload = verify_jwt_token(token)
2721

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66

77
from app.core.config import get_settings
88

9-
JWT_ALGORITHM = "HS256"
10-
119

1210
# Payload follows RFC 7519
1311
# https://www.rfc-editor.org/rfc/rfc7519#section-4.1
@@ -37,7 +35,7 @@ def create_jwt_token(user_id: str) -> JWTToken:
3735
access_token = jwt.encode(
3836
token_payload.model_dump(),
3937
key=get_settings().security.jwt_secret_key.get_secret_value(),
40-
algorithm=JWT_ALGORITHM,
38+
algorithm=get_settings().security.jwt_algorithm,
4139
)
4240

4341
return JWTToken(payload=token_payload, access_token=access_token)
@@ -56,7 +54,7 @@ def verify_jwt_token(token: str) -> JWTTokenPayload:
5654
raw_payload = jwt.decode(
5755
token,
5856
get_settings().security.jwt_secret_key.get_secret_value(),
59-
algorithms=[JWT_ALGORITHM],
57+
algorithms=[get_settings().security.jwt_algorithm],
6058
options={"verify_signature": True},
6159
issuer=get_settings().security.jwt_issuer,
6260
)

0 commit comments

Comments
 (0)