Add complete NexaFabric project structure including: - FastAPI backend with SQLAlchemy models, JWT auth, RBAC, audit logging, and provider interfaces - React + TypeScript frontend with Vite, Tailwind CSS, TanStack Query, and Zustand - Docker Compose configuration for PostgreSQL, Redis, API, worker, frontend, and nginx - GitHub Actions and GitLab CI workflows for testing, linting, building, and security scanning - Environment
45 lines
1.7 KiB
Python
45 lines
1.7 KiB
Python
from datetime import datetime
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, status
|
|
from sqlalchemy import select
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.api.deps import CurrentUser
|
|
from app.core.security import create_access_token, create_refresh_token, verify_password
|
|
from app.db.session import get_db
|
|
from app.models.domain import User
|
|
from app.schemas.domain import LoginRequest, TokenPair, UserRead
|
|
from app.services.audit import write_audit
|
|
|
|
router = APIRouter(prefix="/auth", tags=["auth"])
|
|
|
|
|
|
@router.post("/login", response_model=TokenPair)
|
|
def login(payload: LoginRequest, db: Session = Depends(get_db)) -> TokenPair:
|
|
user = db.scalar(select(User).where(User.email == payload.email))
|
|
if not user or not verify_password(payload.password, user.password_hash):
|
|
if user:
|
|
user.failed_login_attempts += 1
|
|
db.commit()
|
|
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials")
|
|
permissions = sorted({permission for role in user.roles for permission in role.permissions})
|
|
user.failed_login_attempts = 0
|
|
db.commit()
|
|
write_audit(db, action="login", object_type="user", object_id=user.id, user_id=user.id)
|
|
return TokenPair(
|
|
access_token=create_access_token(user.id, permissions),
|
|
refresh_token=create_refresh_token(user.id),
|
|
)
|
|
|
|
|
|
@router.get("/me", response_model=UserRead)
|
|
def me(user: CurrentUser) -> User:
|
|
return user
|
|
|
|
|
|
@router.post("/logout")
|
|
def logout(user: CurrentUser, db: Session = Depends(get_db)) -> dict[str, str]:
|
|
write_audit(db, action="logout", object_type="user", object_id=user.id, user_id=user.id)
|
|
return {"status": "ok", "at": datetime.utcnow().isoformat()}
|
|
|