Skip to content

Commit 5dc3830

Browse files
nit
1 parent 1cf19e6 commit 5dc3830

File tree

11 files changed

+1596
-804
lines changed

11 files changed

+1596
-804
lines changed

backend/app/api/deps.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def get_db() -> Generator[Session, None, None]:
3232
def get_current_user(session: SessionDep, token: TokenDep) -> User:
3333
try:
3434
payload = jwt.decode(
35-
token, settings.SECRET_KEY, algorithms=[security.ALGORITHM]
35+
token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]
3636
)
3737
token_data = TokenPayload(**payload)
3838
except (InvalidTokenError, ValidationError):
@@ -51,8 +51,15 @@ def get_current_user(session: SessionDep, token: TokenDep) -> User:
5151
CurrentUser = Annotated[User, Depends(get_current_user)]
5252

5353

54+
def get_current_active_user(current_user: CurrentUser) -> User:
55+
"""Get current active user (already checked by get_current_user)."""
56+
return current_user
57+
58+
5459
def get_current_active_superuser(current_user: CurrentUser) -> User:
55-
if not current_user.is_superuser:
60+
"""Get current user if they are a superuser."""
61+
from app.models.user import UserRole
62+
if current_user.role not in [UserRole.ADMIN, UserRole.SUPERUSER]:
5663
raise HTTPException(
5764
status_code=403, detail="The user doesn't have enough privileges"
5865
)

backend/app/api/routes/login.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,13 @@ def login_access_token(
3636
elif not user.is_active:
3737
raise HTTPException(status_code=400, detail="Inactive user")
3838
access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
39+
refresh_token_expires = timedelta(days=7) # 7 days for refresh token
3940
return Token(
4041
access_token=security.create_access_token(
4142
user.id, expires_delta=access_token_expires
43+
),
44+
refresh_token=security.create_refresh_token(
45+
user.id, expires_delta=refresh_token_expires
4246
)
4347
)
4448

backend/app/core/config.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -378,10 +378,6 @@ def _enforce_non_default_secrets(self) -> Self:
378378
"version": 1,
379379
"disable_existing_loggers": False,
380380
"formatters": {
381-
"json": {
382-
"()": "pythonjsonlogger.jsonlogger.JsonFormatter",
383-
"format": "%(asctime)s %(levelname)s %(name)s %(message)s",
384-
},
385381
"console": {
386382
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
387383
},

backend/app/core/security.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from app.core.config import settings
2727
from app.core.logging import logger
2828
from app.db import get_db, get_async_db
29-
from app.models import TokenPayload, User, UserRole
29+
from app.models import TokenPayload, User
3030

3131
# Configure logging
3232
logger = logging.getLogger(__name__)
@@ -625,3 +625,7 @@ async def get_current_user_optional(
625625

626626
except (jose_jwt.JWTError, ValidationError):
627627
return None
628+
629+
630+
# Alias for compatibility
631+
decode_token = verify_token

backend/app/models/__init__.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@
4646
MCPServerStatus,
4747
)
4848

49+
from app.models.oauth import (
50+
OAuthState,
51+
OAuthStateBase,
52+
OAuthStateCreate,
53+
OAuthStateUpdate,
54+
OAuthStatePublic,
55+
)
56+
4957
# This ensures that SQLModel knows about all models for migrations
5058
__all__ = [
5159
'BaseDBModel',
@@ -85,4 +93,9 @@
8593
'MCPServerPublic',
8694
'MCPTransportType',
8795
'MCPServerStatus',
96+
'OAuthState',
97+
'OAuthStateBase',
98+
'OAuthStateCreate',
99+
'OAuthStateUpdate',
100+
'OAuthStatePublic',
88101
]

backend/app/models/base.py

Lines changed: 24 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -11,32 +11,27 @@ class BaseDBModel(SQLModel):
1111

1212
id: UUID = Field(
1313
default_factory=uuid4,
14-
sa_column=Column(
15-
PG_UUID(as_uuid=True),
16-
primary_key=True,
17-
server_default=text("gen_random_uuid()"),
18-
19-
index=True,
20-
),
14+
sa_column_kwargs={
15+
"primary_key": True,
16+
"server_default": text("gen_random_uuid()"),
17+
"index": True,
18+
},
19+
sa_type=PG_UUID(as_uuid=True),
2120
)
2221
created_at: datetime = Field(
2322
default_factory=datetime.utcnow,
24-
25-
sa_column=Column(
26-
DateTime(timezone=True),
27-
server_default=func.now(),
28-
29-
),
23+
sa_column_kwargs={
24+
"server_default": func.now(),
25+
},
26+
sa_type=DateTime(timezone=True),
3027
)
3128
updated_at: datetime = Field(
3229
default_factory=datetime.utcnow,
33-
34-
sa_column=Column(
35-
DateTime(timezone=True),
36-
server_default=func.now(),
37-
onupdate=func.now(),
38-
39-
),
30+
sa_column_kwargs={
31+
"server_default": func.now(),
32+
"onupdate": func.now(),
33+
},
34+
sa_type=DateTime(timezone=True),
4035
)
4136

4237
class Config:
@@ -60,20 +55,16 @@ class TimestampMixin(SQLModel):
6055
"""
6156
created_at: datetime = Field(
6257
default_factory=datetime.utcnow,
63-
64-
sa_column=Column(
65-
DateTime(timezone=True),
66-
server_default=func.now(),
67-
68-
),
58+
sa_column_kwargs={
59+
"server_default": func.now(),
60+
},
61+
sa_type=DateTime(timezone=True),
6962
)
7063
updated_at: datetime = Field(
7164
default_factory=datetime.utcnow,
72-
73-
sa_column=Column(
74-
DateTime(timezone=True),
75-
server_default=func.now(),
76-
onupdate=func.now(),
77-
78-
),
65+
sa_column_kwargs={
66+
"server_default": func.now(),
67+
"onupdate": func.now(),
68+
},
69+
sa_type=DateTime(timezone=True),
7970
)

backend/app/models/item.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from uuid import UUID, uuid4
55

66
from pydantic import BaseModel, Field
7+
from sqlalchemy import text
78
from sqlmodel import SQLModel, Field as SQLModelField
89

910
from app.models.base import BaseDBModel
@@ -34,17 +35,10 @@ class ItemUpdate(SQLModel):
3435

3536
class Item(ItemBase, BaseDBModel, table=True):
3637
"""Database model for items."""
37-
__tablename__ = "items"
38+
__tablename__ = "item"
3839

39-
id: UUID = SQLModelField(
40-
default_factory=uuid4,
41-
primary_key=True,
42-
index=True,
43-
nullable=False,
44-
sa_column_kwargs={"server_default": "gen_random_uuid()"},
45-
)
4640
owner_id: UUID = SQLModelField(
47-
..., foreign_key="users.id", description="ID of the user who owns this item"
41+
..., foreign_key="user.id", description="ID of the user who owns this item"
4842
)
4943

5044

backend/app/models/oauth.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"""OAuth state model for CSRF protection."""
2+
from datetime import datetime
3+
from uuid import UUID
4+
5+
from sqlmodel import Field, SQLModel
6+
7+
from app.models.base import BaseDBModel
8+
9+
10+
class OAuthStateBase(SQLModel):
11+
"""Base OAuth state model."""
12+
state_token: str = Field(unique=True, index=True)
13+
provider: str = Field(max_length=50)
14+
redirect_uri: str = Field(max_length=500)
15+
expires_at: datetime
16+
used: bool = Field(default=False)
17+
18+
19+
class OAuthState(OAuthStateBase, BaseDBModel, table=True):
20+
"""OAuth state for CSRF protection stored in database."""
21+
__tablename__ = "oauth_state"
22+
23+
24+
class OAuthStateCreate(OAuthStateBase):
25+
"""Schema for creating OAuth state."""
26+
pass
27+
28+
29+
class OAuthStateUpdate(SQLModel):
30+
"""Schema for updating OAuth state."""
31+
used: bool = True
32+
33+
34+
class OAuthStatePublic(OAuthStateBase):
35+
"""Public schema for OAuth state."""
36+
id: UUID
37+
created_at: datetime
38+
updated_at: datetime

backend/app/models/user.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ class Config:
119119

120120
class UserInDB(UserBase, BaseDBModel, table=True):
121121
"""User model for database representation."""
122-
__tablename__ = "users"
122+
__tablename__ = "user"
123123

124124
# Relationships
125125
refresh_tokens: List["RefreshToken"] = Relationship(back_populates="user")
@@ -211,7 +211,7 @@ class RefreshToken(RefreshTokenBase, BaseDBModel, table=True):
211211
__tablename__ = "refresh_tokens"
212212

213213
user_id: UUID = SQLModelField(
214-
foreign_key="users.id",
214+
foreign_key="user.id",
215215
index=True,
216216
)
217217
user: UserInDB = Relationship(back_populates="refresh_tokens")

backend/pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ dependencies = [
2222
"sentry-sdk[fastapi]<2.0.0,>=1.40.6",
2323
"pyjwt<3.0.0,>=2.8.0",
2424
"aiohttp<4.0.0,>=3.9.0",
25+
"loguru>=0.7.3",
26+
"asyncpg>=0.30.0",
27+
"python-jose[cryptography]>=3.5.0",
2528
]
2629

2730
[tool.uv]

0 commit comments

Comments
 (0)