Fix all 7 launch blockers from security/QA review

SEC-16: Add frontend ports mapping (80:80) to docker-compose.yml
UI-01:  Add routes for Welcome, SettingsIntegrations, MealPlan in App.jsx
SEC-01: Remove traceback leak from auth error responses
SEC-02: Fix require_admin to use is_admin column (was undefined ADMIN_USERNAME)
SEC-03: Add API_TOKEN auth for MCP connector -> backend communication
SEC-04: OAuth state now uses signed JWT tokens (was raw user UUID)
OS-01:  First registered user gets admin immediately during registration

10 files changed, 97 insertions(+), 25 deletions(-)
This commit is contained in:
Claude 2026-06-16 01:50:09 +00:00
parent 50eae17c34
commit 69320e1e82
10 changed files with 97 additions and 25 deletions

View file

@ -1,11 +1,10 @@
from __future__ import annotations
import logging
import traceback
from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.responses import JSONResponse
from sqlalchemy import select
from sqlalchemy import select, func
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_db
@ -26,11 +25,16 @@ async def register(req: RegisterRequest, db: Annotated[AsyncSession, Depends(get
if existing.scalar_one_or_none():
raise HTTPException(status_code=400, detail="Username already taken")
# Grant admin to first user
admin_count = await db.execute(select(func.count(User.id)).where(User.is_admin == True))
is_first_user = (admin_count.scalar() or 0) == 0
user = User(
username=req.username,
display_name=req.display_name,
password_hash=hash_password(req.password),
email=req.email,
is_admin=is_first_user,
)
db.add(user)
await db.flush()
@ -52,9 +56,8 @@ async def register(req: RegisterRequest, db: Annotated[AsyncSession, Depends(get
except HTTPException:
raise
except Exception as e:
tb = traceback.format_exc()
logger.error(f"Registration failed: {e}\n{tb}")
return JSONResponse(status_code=500, content={"detail": str(e), "traceback": tb})
logger.error(f"Registration failed: {e}", exc_info=True)
return JSONResponse(status_code=500, content={"detail": "Internal server error"})
@router.post("/login")
@ -69,9 +72,8 @@ async def login(req: LoginRequest, db: Annotated[AsyncSession, Depends(get_db)])
except HTTPException:
raise
except Exception as e:
tb = traceback.format_exc()
logger.error(f"Login failed: {e}\n{tb}")
return JSONResponse(status_code=500, content={"detail": str(e), "traceback": tb})
logger.error(f"Login failed: {e}", exc_info=True)
return JSONResponse(status_code=500, content={"detail": "Internal server error"})
@router.get("/me")
@ -82,4 +84,5 @@ async def get_me(user: Annotated[User, Depends(get_current_user)]):
"display_name": user.display_name,
"email": user.email,
"timezone": user.timezone,
"is_admin": getattr(user, "is_admin", False),
}