Add RBAC: admin user management, audit log, runtime settings, delete PC

This commit is contained in:
2026-07-16 17:28:04 +07:00
parent c2148f334d
commit d1128f4aa3
16 changed files with 690 additions and 24 deletions
+88 -3
View File
@@ -1,8 +1,7 @@
from fastapi import APIRouter, Depends, HTTPException, status, Response
from fastapi.security import OAuth2PasswordRequestForm
from sqlalchemy.orm import Session
from datetime import datetime, timezone
from typing import Optional
from typing import Optional, List
from database import get_db
from auth import (
@@ -12,10 +11,12 @@ from auth import (
create_refresh_token,
verify_token,
get_current_user,
require_admin,
create_default_admin,
log_action,
)
from models import User
from schemas import UserCreate, UserOut, LoginPayload, Token, TokenRefresh
from schemas import UserCreate, UserOut, LoginPayload, Token, TokenRefresh, UserUpdate, UserListOut
router = APIRouter(prefix="/api/auth", tags=["auth"])
@@ -87,3 +88,87 @@ def me(user: User = Depends(get_current_user)):
def logout(response: Response):
response.delete_cookie("access_token")
return {"ok": True}
# Admin user management
@router.get("/users", response_model=List[UserListOut])
def list_users(
db: Session = Depends(get_db),
admin: User = Depends(require_admin),
):
return db.query(User).order_by(User.username).all()
@router.post("/users", response_model=UserOut, status_code=status.HTTP_201_CREATED)
def create_user(
payload: UserCreate,
db: Session = Depends(get_db),
admin: User = Depends(require_admin),
):
existing = db.query(User).filter(User.username == payload.username).first()
if existing:
raise HTTPException(status_code=400, detail="Username already exists")
user = User(
username=payload.username,
password_hash=get_password_hash(payload.password),
role="viewer",
)
db.add(user)
db.commit()
db.refresh(user)
log_action(db, admin, "user_created", f"Created user {user.username} (id={user.id})")
return user
@router.patch("/users/{user_id}", response_model=UserOut)
def update_user(
user_id: int,
payload: UserUpdate,
db: Session = Depends(get_db),
admin: User = Depends(require_admin),
):
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404, detail="User not found")
if user.id == admin.id and payload.role and payload.role != "admin":
raise HTTPException(status_code=400, detail="Cannot downgrade yourself")
if payload.role:
if payload.role not in ("admin", "viewer"):
raise HTTPException(status_code=400, detail="Invalid role")
user.role = payload.role
if payload.password:
user.password_hash = get_password_hash(payload.password)
db.commit()
db.refresh(user)
log_action(db, admin, "user_updated", f"Updated user {user.username} (id={user.id}), role={user.role}, password_changed={bool(payload.password)}")
return user
@router.delete("/users/{user_id}")
def delete_user(
user_id: int,
db: Session = Depends(get_db),
admin: User = Depends(require_admin),
):
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404, detail="User not found")
if user.id == admin.id:
raise HTTPException(status_code=400, detail="Cannot delete yourself")
# prevent deleting last admin
if user.role == "admin":
admin_count = db.query(User).filter(User.role == "admin").count()
if admin_count <= 1:
raise HTTPException(status_code=400, detail="Cannot delete the last admin")
username = user.username
db.delete(user)
db.commit()
log_action(db, admin, "user_deleted", f"Deleted user {username} (id={user_id})")
return {"ok": True}