Initial commit: CI Agent competitive-intelligence monitoring app

FastAPI + Celery + Next.js + Postgres/Redis app with company monitoring,
source collection, LLM-based change analysis, enrichment, and account
security (Turnstile, escalating lockout, email verification).
This commit is contained in:
2026-08-05 10:48:20 -04:00
commit 1a4c80958f
365 changed files with 43541 additions and 0 deletions
+69
View File
@@ -0,0 +1,69 @@
"""FastAPI dependency implementing the `AuthProvider` contract described in
ARCHITECTURE.md: `get_current_user` always returns a `User` row or raises
401, regardless of caller. This is the seam a future Firebase Auth
integration would replace.
When AUTH_MODE=local (the default), the fixed local-dev user is only
returned to a request that's actually from loopback (see
`app.core.security.is_localhost`) - anyone reaching the API from a LAN or
WAN connection still needs a real bearer token, even with that setting.
AUTH_MODE=jwt disables the loopback convenience entirely (required in
production, see `Settings._forbid_local_auth_in_production`).
"""
from __future__ import annotations
from fastapi import Depends, Header, HTTPException, Request, status
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.config import Settings, get_settings
from app.core.security import (
InvalidTokenError,
TokenType,
decode_token,
get_client_ip,
is_localhost,
)
from app.db.session import get_db
from app.models.user import User
from app.repositories.user_repository import UserRepository
from app.services.auth_service import get_or_create_local_user
async def get_current_user(
request: Request,
authorization: str | None = Header(default=None),
settings: Settings = Depends(get_settings),
db: AsyncSession = Depends(get_db),
) -> User:
if settings.auth_mode == "local" and is_localhost(request, settings):
return await get_or_create_local_user(db, get_client_ip(request, settings))
if authorization is None or not authorization.lower().startswith("bearer "):
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated")
token = authorization.split(" ", 1)[1]
try:
decoded = decode_token(token, settings, TokenType.ACCESS)
except InvalidTokenError as exc:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid or expired access token",
) from exc
repo = UserRepository(db)
user = await repo.get_by_id(decoded.user_id)
if user is None or not user.is_active:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid or expired access token",
)
return user
async def require_admin(user: User = Depends(get_current_user)) -> User:
if not user.is_admin:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, detail="Admin privileges required"
)
return user