- FastAPI backend: auth, findings, dashboard, attack paths, footprint, AI translator, risk calculator, PDF report generator - Next.js frontend: Vault dashboard, login, findings table, finding detail with AI coach, digital footprint, reports - PostgreSQL data model: tenants, users, assets, findings, risk scores, audit reports, attack paths - Docker Compose + Dockerfiles for all services - Demo seed data: Acme Corp with 6 findings and 90-day risk score history - AI Risk Translator (OpenAI/Anthropic) with plain-English business impact - Role-based access: executive / it_admin / trustos_admin - Scope-lock engine: authorization required before any assessment Stage 1-8 complete: Phase 1 Vault Audit product ready
38 lines
1.7 KiB
Python
38 lines
1.7 KiB
Python
from fastapi import APIRouter, Depends, HTTPException, status
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy import select
|
|
from datetime import datetime
|
|
|
|
from app.db.session import get_db
|
|
from app.models.models import User
|
|
from app.schemas.schemas import LoginRequest, TokenResponse, UserMe
|
|
from app.core.security import verify_password, create_access_token, get_current_user_payload
|
|
|
|
router = APIRouter(prefix="/auth", tags=["auth"])
|
|
|
|
|
|
@router.post("/login", response_model=TokenResponse)
|
|
async def login(body: LoginRequest, db: AsyncSession = Depends(get_db)):
|
|
result = await db.execute(select(User).where(User.email == body.email, User.is_active == True))
|
|
user: User | None = result.scalar_one_or_none()
|
|
if not user or not verify_password(body.password, user.hashed_password):
|
|
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials")
|
|
user.last_login = datetime.utcnow()
|
|
await db.commit()
|
|
token = create_access_token({"sub": user.id, "role": user.role.value, "tenant_id": user.tenant_id})
|
|
return TokenResponse(
|
|
access_token=token,
|
|
role=user.role.value,
|
|
tenant_id=user.tenant_id,
|
|
full_name=user.full_name
|
|
)
|
|
|
|
|
|
@router.get("/me", response_model=UserMe)
|
|
async def get_me(payload: dict = Depends(get_current_user_payload), db: AsyncSession = Depends(get_db)):
|
|
result = await db.execute(select(User).where(User.id == payload["sub"]))
|
|
user: User | None = result.scalar_one_or_none()
|
|
if not user:
|
|
raise HTTPException(status_code=404, detail="User not found")
|
|
return UserMe(id=user.id, email=user.email, full_name=user.full_name, role=user.role, tenant_id=user.tenant_id)
|