feat: Discord/Gmail 알림 기능 추가
Some checks failed
Build and Push Images / build-backend (push) Has been cancelled
Some checks failed
Build and Push Images / build-backend (push) Has been cancelled
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
from fastapi import FastAPI, HTTPException, Depends, status
|
||||
from contextlib import asynccontextmanager
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from pydantic import BaseModel
|
||||
@@ -12,7 +13,16 @@ import secrets
|
||||
import string
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
app = FastAPI(title="Web Portal API")
|
||||
from notifier import notify_both, notify_email_only, notify_discord_only
|
||||
from monitor import start_scheduler
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
scheduler = start_scheduler()
|
||||
yield
|
||||
scheduler.shutdown()
|
||||
|
||||
app = FastAPI(title="Web Portal API", lifespan=lifespan)
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
@@ -170,6 +180,16 @@ def login(req: LoginRequest, conn=Depends(get_db)):
|
||||
)
|
||||
conn.commit()
|
||||
if locked:
|
||||
import asyncio
|
||||
asyncio.create_task(notify_discord_only(
|
||||
title="🔒 계정 잠금 발생",
|
||||
message=(
|
||||
f"사용자: `{req.username}`\n"
|
||||
f"사유: 비밀번호 {MAX_LOGIN_ATTEMPTS}회 오류로 계정이 잠겼습니다.\n"
|
||||
f"관리자 페이지에서 잠금 해제 또는 임시 비밀번호를 발급해주세요."
|
||||
),
|
||||
color=0xe74c3c
|
||||
))
|
||||
raise HTTPException(status_code=403, detail="Account locked due to too many failed attempts. Please contact admin.")
|
||||
remaining = MAX_LOGIN_ATTEMPTS - attempts
|
||||
raise HTTPException(status_code=401, detail=f"Invalid credentials. {remaining} attempts remaining.")
|
||||
@@ -338,10 +358,12 @@ def admin_change_password(user_id: int, data: AdminPasswordChange, token=Depends
|
||||
|
||||
# ─── Admin: 임시 비밀번호 발급 ───────────────────────────
|
||||
@app.post("/api/admin/users/{user_id}/reset-password")
|
||||
def reset_password(user_id: int, token=Depends(require_admin), conn=Depends(get_db)):
|
||||
async def reset_password(user_id: int, token=Depends(require_admin), conn=Depends(get_db)):
|
||||
temp_pw = generate_temp_password()
|
||||
hashed = bcrypt.hashpw(temp_pw.encode(), bcrypt.gensalt()).decode()
|
||||
cur = conn.cursor()
|
||||
cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
|
||||
cur.execute("SELECT username FROM users WHERE id=%s", (user_id,))
|
||||
user = cur.fetchone()
|
||||
cur.execute(
|
||||
"""UPDATE users SET password_hash=%s, must_change_password=TRUE,
|
||||
login_attempts=0, is_locked=FALSE, password_change_requested=FALSE
|
||||
@@ -349,6 +371,16 @@ def reset_password(user_id: int, token=Depends(require_admin), conn=Depends(get_
|
||||
(hashed, user_id)
|
||||
)
|
||||
conn.commit()
|
||||
if user:
|
||||
import asyncio
|
||||
asyncio.create_task(notify_discord_only(
|
||||
title="🔑 임시 비밀번호 발급",
|
||||
message=(
|
||||
f"관리자 `{token['username']}` 이(가) 임시 비밀번호를 발급했습니다.\n"
|
||||
f"대상 사용자: `{user['username']}`"
|
||||
),
|
||||
color=0x3498db
|
||||
))
|
||||
return {"ok": True, "temp_password": temp_pw}
|
||||
|
||||
# ─── Admin: 계정 잠금 해제 ───────────────────────────────
|
||||
@@ -390,6 +422,37 @@ def update_user_pages(user_id: int, data: AccessUpdate, token=Depends(require_ad
|
||||
conn.commit()
|
||||
return {"ok": True}
|
||||
|
||||
@app.get("/api/admin/notify-test")
|
||||
async def notify_test(token=Depends(require_admin)):
|
||||
# Discord + Gmail (Pod 이상/복구 테스트)
|
||||
await notify_both(
|
||||
title="✅ [Discord+Gmail] 알림 테스트",
|
||||
message=(
|
||||
f"관리자 `{token['username']}` 이(가) 알림 테스트를 실행했습니다.\n"
|
||||
f"Pod 이상/복구 알림 채널입니다."
|
||||
),
|
||||
color=0x2ecc71
|
||||
)
|
||||
# Gmail only (인증서 만료 테스트)
|
||||
await notify_email_only(
|
||||
title="✅ [Gmail 전용] 알림 테스트",
|
||||
message=(
|
||||
f"인증서 만료 임박 알림 채널입니다.\n"
|
||||
f"Gmail로만 발송됩니다."
|
||||
),
|
||||
color=0xf39c12
|
||||
)
|
||||
# Discord only (계정 잠금/임시PW 테스트)
|
||||
await notify_discord_only(
|
||||
title="✅ [Discord 전용] 알림 테스트",
|
||||
message=(
|
||||
f"계정 잠금/임시 비밀번호 발급 알림 채널입니다.\n"
|
||||
f"Discord로만 발송됩니다."
|
||||
),
|
||||
color=0x3498db
|
||||
)
|
||||
return {"ok": True, "message": "채널별 알림 테스트 완료 (Discord+Gmail / Gmail전용 / Discord전용)"}
|
||||
|
||||
@app.get("/health")
|
||||
def health():
|
||||
return {"status": "ok"}
|
||||
|
||||
Reference in New Issue
Block a user