Files
iDRAC_Info/backend/routes/admin.py
2025-10-05 17:37:51 +09:00

127 lines
3.9 KiB
Python

# backend/routes/admin.py
from __future__ import annotations
import logging
from functools import wraps
from typing import Callable
from flask import (
Blueprint,
render_template,
redirect,
url_for,
flash,
abort,
request,
current_app,
)
from flask_login import login_required, current_user
from backend.models.user import User, db
admin_bp = Blueprint("admin", __name__)
# Blueprint 등록
def register_admin_routes(app):
app.register_blueprint(admin_bp)
# 관리자 권한 데코레이터
def admin_required(view_func: Callable):
@wraps(view_func)
def wrapper(*args, **kwargs):
if not current_user.is_authenticated:
return redirect(url_for("auth.login"))
if not getattr(current_user, "is_admin", False):
flash("관리자 권한이 필요합니다.", "danger")
return redirect(url_for("main.index"))
return view_func(*args, **kwargs)
return wrapper
# 관리자 대시보드
@admin_bp.route("/admin", methods=["GET"])
@login_required
@admin_required
def admin_panel():
users = db.session.query(User).order_by(User.id.asc()).all()
return render_template("admin.html", users=users)
# 사용자 승인
@admin_bp.route("/admin/approve/<int:user_id>", methods=["GET"])
@login_required
@admin_required
def approve_user(user_id: int):
user = db.session.get(User, user_id)
if not user:
abort(404)
user.is_active = True
db.session.commit()
flash("사용자가 승인되었습니다.", "success")
logging.info("✅ 승인된 사용자: %s (id=%s)", user.username, user.id)
return redirect(url_for("admin.admin_panel"))
# 사용자 삭제
@admin_bp.route("/admin/delete/<int:user_id>", methods=["GET"])
@login_required
@admin_required
def delete_user(user_id: int):
user = db.session.get(User, user_id)
if not user:
abort(404)
username = user.username
db.session.delete(user)
db.session.commit()
flash("사용자가 삭제되었습니다.", "success")
logging.info("🗑 삭제된 사용자: %s (id=%s)", username, user_id)
return redirect(url_for("admin.admin_panel"))
# ▼▼▼ 사용자 비밀번호 변경(관리자용) ▼▼▼
@admin_bp.route("/admin/users/<int:user_id>/reset_password", methods=["POST"])
@login_required
@admin_required
def reset_password(user_id: int):
"""
admin.html에서 각 사용자 행 아래 폼으로부터 POST:
- name="new_password"
- name="confirm_password"
CSRF는 템플릿에서 {{ csrf_token() }} 또는 {{ form.hidden_tag() }}로 포함되어야 합니다.
"""
new_pw = (request.form.get("new_password") or "").strip()
confirm = (request.form.get("confirm_password") or "").strip()
# 서버측 검증
if not new_pw or not confirm:
flash("비밀번호와 확인 값을 모두 입력하세요.", "warning")
return redirect(url_for("admin.admin_panel"))
if new_pw != confirm:
flash("비밀번호 확인이 일치하지 않습니다.", "warning")
return redirect(url_for("admin.admin_panel"))
if len(new_pw) < 8:
flash("비밀번호는 최소 8자 이상이어야 합니다.", "warning")
return redirect(url_for("admin.admin_panel"))
user = db.session.get(User, user_id)
if not user:
abort(404)
try:
# passlib(Argon2id) 기반 set_password 사용 (models.user에 구현됨)
user.set_password(new_pw)
db.session.commit()
flash(f"사용자(ID={user.id}) 비밀번호를 변경했습니다.", "success")
current_app.logger.info(
"ADMIN: reset password for user_id=%s by admin_id=%s",
user.id, current_user.id
)
except Exception as e:
db.session.rollback()
current_app.logger.exception("ADMIN: reset password failed: %s", e)
flash("비밀번호 변경 중 오류가 발생했습니다.", "danger")
return redirect(url_for("admin.admin_panel"))