Backend (FastAPI, SQLAlchemy 2, Alembic, PostgreSQL 16): - организации, VRF, префиксы (дерево, использование, автоназначение), адреса, операторы связи, устройства и типы устройств; JWT, роли admin/viewer; - VRF принадлежит организации (составной FK), смена VRF у префикса переносит поддерево, имя VRF уникально в организации; - журнал аудита: поиск и фильтры, ротация (срок/количество), очистка по паролю с блокировкой, IP клиента и метаданные запроса (X-Forwarded-For только от TRUSTED_PROXIES). UI (web/, без сборки): экраны и диалоги по макетам «IPAM Manager», кликабельные строки реестров, локальные шрифты IBM Plex, собственные выпадающие списки. Окружение: docker-compose (postgres + app), миграции Alembic 0001-0004, scripts/gen_env.py, scripts/seed_demo.py, 11 автотестов (pytest). Документация: README.md и docs/changes/001-005 (планы и итоги). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
29 lines
1.2 KiB
Python
29 lines
1.2 KiB
Python
from fastapi import APIRouter, Depends, HTTPException
|
|
from sqlalchemy import select
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.db import get_db
|
|
from app.models import User
|
|
from app.schemas import LoginIn, TokenOut, UserOut
|
|
from app.security import create_token, current_user, verify_password
|
|
from app.services import ANONYMOUS, audit
|
|
|
|
router = APIRouter(prefix="/auth", tags=["auth"])
|
|
|
|
|
|
@router.post("/login", response_model=TokenOut)
|
|
def login(body: LoginIn, db: Session = Depends(get_db)):
|
|
user = db.scalar(select(User).where(User.username == body.username, User.is_active))
|
|
if user is None or not verify_password(body.password, user.password_hash):
|
|
audit(db, ANONYMOUS, "session", None, "failed", body.username[:100], message="Неудачная попытка входа в UI")
|
|
db.commit()
|
|
raise HTTPException(401, "Неверный логин или пароль")
|
|
audit(db, user, "session", None, "login", user.username, message=f"Вход в UI: {user.username}")
|
|
db.commit()
|
|
return TokenOut(access_token=create_token(user))
|
|
|
|
|
|
@router.get("/me", response_model=UserOut)
|
|
def me(user: User = Depends(current_user)):
|
|
return user
|