Skip to content

Commit c35cbe0

Browse files
authored
Remove jwt bug fixes (#2)
* remove jwt completely, fix some bugs * remove jwt references * some fixes * add tests * linting * type fixes * typing fixes
1 parent bd8fdcd commit c35cbe0

33 files changed

+3323
-636
lines changed

.gitignore

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -166,9 +166,7 @@ cython_debug/
166166

167167
.ruff_cache
168168

169-
/tests/local
170-
171-
/crudadmin/_templates
172-
173169
uv.lock
174-
.python-version
170+
.python-version
171+
172+
local_test

crudadmin/admin_interface/admin_site.py

Lines changed: 32 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import logging
2-
from datetime import datetime, timedelta, timezone
3-
from typing import Any, AsyncGenerator, Callable, Dict, Optional, cast
2+
from collections.abc import AsyncGenerator, Callable
3+
from datetime import UTC, datetime
4+
from typing import Any, Dict, Optional, cast
45

56
from fastapi import APIRouter, Cookie, Depends, Request, Response
67
from fastapi.responses import RedirectResponse
@@ -40,10 +41,10 @@ class AdminSite:
4041
Args:
4142
database_config: Database configuration for admin interface
4243
templates_directory: Path to template files
43-
models: Dictionary of registered models and their configurations
44-
admin_authentication: Authentication handler instance
45-
mount_path: URL path prefix for admin interface (e.g. "/admin")
46-
theme: UI theme name ("dark-theme" or "light-theme")
44+
models: Dictionary of registered models
45+
admin_authentication: Authentication handler
46+
mount_path: URL prefix for admin routes
47+
theme: Active UI theme
4748
secure_cookies: Enable secure cookie flags
4849
event_integration: Optional event logging integration
4950
@@ -54,7 +55,6 @@ class AdminSite:
5455
models: Dictionary of registered models
5556
admin_user_service: Service for user management
5657
admin_authentication: Authentication handler
57-
token_service: JWT token service
5858
mount_path: URL prefix for admin routes
5959
theme: Active UI theme
6060
event_integration: Event logging handler
@@ -108,27 +108,28 @@ def __init__(
108108
secure_cookies: bool,
109109
event_integration: Optional[Any] = None,
110110
) -> None:
111-
self.db_config = database_config
112-
self.router = APIRouter()
113-
self.templates = Jinja2Templates(directory=templates_directory)
114-
self.models = models
115-
self.admin_user_service = AdminUserService(db_config=database_config)
116-
self.admin_authentication = admin_authentication
111+
self.db_config: DatabaseConfig = database_config
112+
self.router: APIRouter = APIRouter()
113+
self.templates: Jinja2Templates = Jinja2Templates(directory=templates_directory)
114+
self.models: Dict[str, Any] = models
115+
self.admin_user_service: AdminUserService = AdminUserService(
116+
db_config=database_config
117+
)
118+
self.admin_authentication: AdminAuthentication = admin_authentication
117119
self.admin_user_service = admin_authentication.user_service
118-
self.token_service = admin_authentication.token_service
119120

120-
self.mount_path = mount_path
121-
self.theme = theme
122-
self.event_integration = event_integration
121+
self.mount_path: str = mount_path
122+
self.theme: str = theme
123+
self.event_integration: Optional[Any] = event_integration
123124

124-
self.session_manager = SessionManager(
125+
self.session_manager: SessionManager = SessionManager(
125126
self.db_config,
126127
max_sessions_per_user=5,
127128
session_timeout_minutes=30,
128129
cleanup_interval_minutes=15,
129130
)
130131

131-
self.secure_cookies = secure_cookies
132+
self.secure_cookies: bool = secure_cookies
132133

133134
def setup_routes(self) -> None:
134135
"""
@@ -205,7 +206,7 @@ def login_page(self) -> EndpointCallable:
205206
206207
Notes:
207208
- Validates credentials and creates user session on success
208-
- Sets secure cookies with tokens
209+
- Sets secure cookies with session ID
209210
- Logs login attempts if event tracking enabled
210211
"""
211212

@@ -237,13 +238,7 @@ async def login_page_inner(
237238
)
238239

239240
request.state.user = user
240-
logger.info("User authenticated successfully, creating token")
241-
access_token_expires = timedelta(
242-
minutes=self.token_service.ACCESS_TOKEN_EXPIRE_MINUTES
243-
)
244-
access_token = await self.token_service.create_access_token(
245-
data={"sub": user["username"]}, expires_delta=access_token_expires
246-
)
241+
logger.info("User authenticated successfully, creating session")
247242

248243
try:
249244
logger.info("Creating user session...")
@@ -253,7 +248,7 @@ async def login_page_inner(
253248
metadata={
254249
"login_type": "password",
255250
"username": user["username"],
256-
"creation_time": datetime.now(timezone.utc).isoformat(),
251+
"creation_time": datetime.now(UTC).isoformat(),
257252
},
258253
)
259254

@@ -267,24 +262,16 @@ async def login_page_inner(
267262
url=f"/{self.mount_path}/", status_code=303
268263
)
269264

270-
max_age_int = int(access_token_expires.total_seconds())
271-
272-
response.set_cookie(
273-
key="access_token",
274-
value=f"Bearer {access_token}",
275-
httponly=True,
276-
secure=self.secure_cookies,
277-
max_age=max_age_int,
278-
path=f"/{self.mount_path}",
279-
samesite="lax",
265+
session_timeout_seconds = int(
266+
self.session_manager.session_timeout.total_seconds()
280267
)
281268

282269
response.set_cookie(
283270
key="session_id",
284271
value=session.session_id,
285272
httponly=True,
286273
secure=self.secure_cookies,
287-
max_age=max_age_int,
274+
max_age=session_timeout_seconds,
288275
path=f"/{self.mount_path}",
289276
samesite="lax",
290277
)
@@ -341,31 +328,9 @@ async def logout_endpoint_inner(
341328
request: Request,
342329
response: Response,
343330
db: AsyncSession = Depends(self.db_config.get_admin_db),
344-
access_token: Optional[str] = Cookie(None),
345331
session_id: Optional[str] = Cookie(None),
346332
event_integration: Optional[Any] = Depends(lambda: self.event_integration),
347333
) -> RouteResponse:
348-
if access_token:
349-
token = (
350-
access_token.replace("Bearer ", "")
351-
if access_token.startswith("Bearer ")
352-
else access_token
353-
)
354-
token_data = await self.token_service.verify_token(token, db)
355-
if token_data:
356-
if "@" in token_data.username_or_email:
357-
user = await self.db_config.crud_users.get(
358-
db=db, email=token_data.username_or_email
359-
)
360-
else:
361-
user = await self.db_config.crud_users.get(
362-
db=db, username=token_data.username_or_email
363-
)
364-
if user:
365-
request.state.user = user
366-
367-
await self.token_service.blacklist_token(token, db)
368-
369334
if session_id:
370335
await self.session_manager.terminate_session(
371336
db=db, session_id=session_id
@@ -375,7 +340,6 @@ async def logout_endpoint_inner(
375340
url=f"/{self.mount_path}/login", status_code=303
376341
)
377342

378-
response.delete_cookie(key="access_token", path=f"/{self.mount_path}")
379343
response.delete_cookie(key="session_id", path=f"/{self.mount_path}")
380344

381345
return response
@@ -401,27 +365,18 @@ async def admin_login_page_inner(
401365
db: AsyncSession = Depends(self.db_config.get_admin_db),
402366
) -> RouteResponse:
403367
try:
404-
access_token = request.cookies.get("access_token")
405368
session_id = request.cookies.get("session_id")
406369

407-
if access_token and session_id:
408-
token = (
409-
access_token.split(" ")[1]
410-
if access_token.startswith("Bearer ")
411-
else access_token
370+
if session_id:
371+
is_valid_session = await self.session_manager.validate_session(
372+
db=db, session_id=session_id
412373
)
413-
token_data = await self.token_service.verify_token(token, db)
414374

415-
if token_data:
416-
is_valid_session = await self.session_manager.validate_session(
417-
db=db, session_id=session_id
375+
if is_valid_session:
376+
return RedirectResponse(
377+
url=f"/{self.mount_path}/", status_code=303
418378
)
419379

420-
if is_valid_session:
421-
return RedirectResponse(
422-
url=f"/{self.mount_path}/", status_code=303
423-
)
424-
425380
except Exception:
426381
pass
427382

crudadmin/admin_interface/auth.py

Lines changed: 20 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
from fastapi.security import OAuth2PasswordBearer
66
from sqlalchemy.ext.asyncio import AsyncSession
77

8-
from ..admin_token.schemas import AdminTokenBlacklistCreate, AdminTokenBlacklistUpdate
9-
from ..admin_token.service import TokenService
108
from ..admin_user.schemas import (
119
AdminUserCreate,
1210
AdminUserRead,
@@ -16,6 +14,7 @@
1614
from ..admin_user.service import AdminUserService
1715
from ..core.db import DatabaseConfig
1816
from ..core.exceptions import ForbiddenException, UnauthorizedException
17+
from ..session.manager import SessionManager
1918
from ..session.schemas import AdminSessionCreate, AdminSessionUpdate
2019

2120
logger = logging.getLogger(__name__)
@@ -26,16 +25,16 @@ def __init__(
2625
self,
2726
database_config: DatabaseConfig,
2827
user_service: AdminUserService,
29-
token_service: TokenService,
28+
session_manager: SessionManager,
3029
oauth2_scheme: OAuth2PasswordBearer,
3130
event_integration=None,
3231
) -> None:
3332
self.db_config = database_config
3433
self.user_service = user_service
35-
self.token_service = token_service
3634
self.oauth2_scheme = oauth2_scheme
3735
self.auth_models = {}
3836
self.event_integration = event_integration
37+
self.session_manager = session_manager
3938

4039
self.auth_models[self.db_config.AdminUser.__name__] = {
4140
"model": self.db_config.AdminUser,
@@ -46,15 +45,6 @@ def __init__(
4645
"delete_schema": None,
4746
}
4847

49-
self.auth_models[self.db_config.AdminTokenBlacklist.__name__] = {
50-
"model": self.db_config.AdminTokenBlacklist,
51-
"crud": self.db_config.crud_token_blacklist,
52-
"create_schema": AdminTokenBlacklistCreate,
53-
"update_schema": AdminTokenBlacklistUpdate,
54-
"update_internal_schema": AdminTokenBlacklistUpdate,
55-
"delete_schema": None,
56-
}
57-
5848
self.auth_models[self.db_config.AdminSession.__name__] = {
5949
"model": self.db_config.AdminSession,
6050
"crud": self.db_config.crud_sessions,
@@ -68,33 +58,30 @@ def get_current_user(self):
6858
async def get_current_user_inner(
6959
request: Request,
7060
db: AsyncSession = Depends(self.db_config.get_admin_db),
71-
access_token: Optional[str] = Cookie(None),
61+
session_id: Optional[str] = Cookie(None),
7262
) -> Optional[AdminUserRead]:
73-
logger.debug(f"Starting get_current_user with token: {access_token}")
63+
logger.debug(f"Starting get_current_user with session_id: {session_id}")
7464

75-
if not access_token:
76-
logger.debug("No access token found")
65+
if not session_id:
66+
logger.debug("No session_id found")
7767
raise UnauthorizedException("Not authenticated")
7868

79-
token = None
80-
if access_token.startswith("Bearer "):
81-
token = access_token.split(" ")[1]
82-
else:
83-
token = access_token
69+
is_valid_session = await self.session_manager.validate_session(
70+
db, session_id
71+
)
72+
if not is_valid_session:
73+
logger.debug("Session validation failed")
74+
raise UnauthorizedException("Could not validate credentials")
8475

85-
token_data = await self.token_service.verify_token(token, db)
86-
if token_data is None:
87-
logger.debug("Token verification failed")
76+
session_data = await self.session_manager.get_session_metadata(
77+
db, session_id
78+
)
79+
if not session_data or "user_id" not in session_data:
80+
logger.debug("User ID not found in session data")
8881
raise UnauthorizedException("Could not validate credentials")
8982

90-
if "@" in token_data.username_or_email:
91-
user = await self.db_config.crud_users.get(
92-
db=db, email=token_data.username_or_email
93-
)
94-
else:
95-
user = await self.db_config.crud_users.get(
96-
db=db, username=token_data.username_or_email
97-
)
83+
user_id = session_data["user_id"]
84+
user = await self.db_config.crud_users.get(db=db, id=user_id)
9885

9986
if user:
10087
logger.debug("User found")

0 commit comments

Comments
 (0)