2ce7862058
Replace complex JWT + refresh token authentication with simple session-based auth using signed cookies. **Removed:** - JWT token service (jwt_service.py) - Refresh token store (refresh_store.py) - Refresh token model and database table - JWKS fetching and OIDC token verification - python-jose dependency **Added:** - Session service (session.py) with HMAC-SHA256 signed cookies - Auth dependencies module for shared auth logic - Session-based auth endpoints **Updated:** - All API endpoints to use session-based auth - Config: removed JWT settings, added SESSION_SECRET/SESSION_TTL_HOURS - Tests: rewritten for session-based flow - Frontend: no changes needed (already uses cookies) Quality gates: ruff ✓, mypy ✓, typecheck ✓, lint ✓
75 lines
2.4 KiB
Python
75 lines
2.4 KiB
Python
import uuid
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, status
|
|
from pydantic import BaseModel, ConfigDict
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from src.auth.dependencies import get_current_user_id, get_db_session
|
|
from src.models.user import User
|
|
from src.models.user_config import UserConfig
|
|
|
|
router = APIRouter(prefix="/users/me", tags=["user-config"])
|
|
|
|
|
|
async def _get_user(session: AsyncSession, user_id: uuid.UUID) -> User:
|
|
user = await session.get(User, user_id)
|
|
if user is None:
|
|
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="user not found")
|
|
return user
|
|
|
|
|
|
async def _get_or_create_config(session: AsyncSession, user_id: uuid.UUID) -> UserConfig:
|
|
result = await session.execute(select(UserConfig).where(UserConfig.user_id == user_id))
|
|
config = result.scalar_one_or_none()
|
|
if config is None:
|
|
config = UserConfig(user_id=user_id, config={})
|
|
session.add(config)
|
|
await session.commit()
|
|
await session.refresh(config)
|
|
return config
|
|
|
|
|
|
class UserConfigResponse(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
default_editor: str | None = None
|
|
theme: str = "system"
|
|
git_user_name: str | None = None
|
|
git_user_email: str | None = None
|
|
|
|
|
|
class UserConfigUpdate(BaseModel):
|
|
default_editor: str | None = None
|
|
theme: str | None = None
|
|
git_user_name: str | None = None
|
|
git_user_email: str | None = None
|
|
|
|
|
|
@router.get("/config", response_model=UserConfigResponse)
|
|
async def get_user_config(
|
|
user_id: uuid.UUID = Depends(get_current_user_id),
|
|
session: AsyncSession = Depends(get_db_session),
|
|
) -> UserConfigResponse:
|
|
_user = await _get_user(session, user_id)
|
|
config = await _get_or_create_config(session, user_id)
|
|
return UserConfigResponse.model_validate(config.config)
|
|
|
|
|
|
@router.patch("/config", response_model=UserConfigResponse)
|
|
async def update_user_config(
|
|
data: UserConfigUpdate,
|
|
user_id: uuid.UUID = Depends(get_current_user_id),
|
|
session: AsyncSession = Depends(get_db_session),
|
|
) -> UserConfigResponse:
|
|
_user = await _get_user(session, user_id)
|
|
config = await _get_or_create_config(session, user_id)
|
|
|
|
# Merge updates
|
|
update_data = data.model_dump(exclude_unset=True, exclude_none=True)
|
|
config.config.update(update_data)
|
|
|
|
await session.commit()
|
|
await session.refresh(config)
|
|
return UserConfigResponse.model_validate(config.config)
|