260706_1_계정, 로그인, 보안 초안 작성
This commit is contained in:
@@ -9,24 +9,31 @@ FastAPI 애플리케이션 진입점
|
||||
- 라우터 등록 (페이지별 API)
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
from contextlib import asynccontextmanager
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi import Depends, FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from A06_Login.A06_Login_Router import router as a06_login_router
|
||||
from A07_Register.A07_Register_Router import router as a07_register_router
|
||||
from A08_Support.A08_Support_Router import router as a08_support_router
|
||||
from A09_Security.A09_Security_Router import router as a09_security_router
|
||||
from B03_FileInput.B03_FileInput_Router import router as b03_file_input_router
|
||||
from B04_wf1_Surface.B04_wf1_Surface_Router import router as b04_surface_router
|
||||
from B05_wf2_Route.B05_wf2_Route_Router import router as b05_route_router
|
||||
from B06_wf3_ProfileCross.B06_wf3_ProfileCross_Router import router as b06_section_router
|
||||
from config.config_db import close_db_pool, init_db_pool
|
||||
from common_util.common_util_auth import verify_session
|
||||
from config.config_db import close_db_pool, get_db_pool, init_db_pool
|
||||
|
||||
# 설정 import
|
||||
from config.config_system import (
|
||||
ADMIN_EMAIL,
|
||||
CORS_ORIGINS,
|
||||
DEBUG,
|
||||
ENVIRONMENT,
|
||||
@@ -47,20 +54,15 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def build_frontend() -> bool:
|
||||
"""프론트엔드 빌드 (npm run build from config/)"""
|
||||
"""프론트엔드 빌드 (npm run build from project root)"""
|
||||
root_dir = Path(__file__).resolve().parent
|
||||
config_dir = root_dir / "config"
|
||||
|
||||
if not config_dir.exists():
|
||||
logger.warning(f"⚠ config 디렉토리 없음: {config_dir}")
|
||||
return False
|
||||
|
||||
logger.info("[Frontend] npm run build 시작...")
|
||||
try:
|
||||
result = subprocess.run(
|
||||
"npm run build",
|
||||
shell=True,
|
||||
cwd=str(config_dir),
|
||||
cwd=str(root_dir),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
@@ -82,13 +84,8 @@ def build_frontend() -> bool:
|
||||
|
||||
|
||||
def serve_frontend_dev() -> None:
|
||||
"""프론트엔드 개발 서버 실행 (npm run dev from config/) — 선택 사항"""
|
||||
"""프론트엔드 개발 서버 실행 (npm run dev from project root) — 선택 사항"""
|
||||
root_dir = Path(__file__).resolve().parent
|
||||
config_dir = root_dir / "config"
|
||||
|
||||
if not config_dir.exists():
|
||||
logger.warning(f"⚠ config 디렉토리 없음, 개발 서버 스킵: {config_dir}")
|
||||
return
|
||||
|
||||
if ENVIRONMENT == "production":
|
||||
logger.info("[Frontend] 프로덕션 환경: 개발 서버 실행 스킵")
|
||||
@@ -99,7 +96,7 @@ def serve_frontend_dev() -> None:
|
||||
subprocess.Popen(
|
||||
"npm run dev",
|
||||
shell=True,
|
||||
cwd=str(config_dir),
|
||||
cwd=str(root_dir),
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
@@ -108,6 +105,32 @@ def serve_frontend_dev() -> None:
|
||||
logger.warning(f"⚠ 프론트엔드 개발 서버 실행 실패: {e}")
|
||||
|
||||
|
||||
async def cleanup_expired_sessions() -> None:
|
||||
"""매시간 만료된 세션과 사용 완료 OTP를 정리한다."""
|
||||
while True:
|
||||
await asyncio.sleep(3600)
|
||||
pool = get_db_pool()
|
||||
async with pool.acquire() as connection, connection.cursor() as cursor:
|
||||
await cursor.execute("DELETE FROM sessions WHERE expires_at < CURRENT_TIMESTAMP")
|
||||
await cursor.execute(
|
||||
"""DELETE FROM email_otps
|
||||
WHERE expires_at < CURRENT_TIMESTAMP
|
||||
OR consumed_at < DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 1 DAY)"""
|
||||
)
|
||||
await cursor.execute(
|
||||
"""DELETE FROM login_logs WHERE
|
||||
(status = 'SUCCESS'
|
||||
AND login_at < DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 1 YEAR))
|
||||
OR (status = 'FAILURE'
|
||||
AND login_at < DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 6 MONTH))"""
|
||||
)
|
||||
await cursor.execute(
|
||||
"""DELETE FROM activity_logs
|
||||
WHERE action_at < DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 2 YEAR)"""
|
||||
)
|
||||
await connection.commit()
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# 라이프사이클: 앱 시작/종료
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
@@ -129,11 +152,20 @@ async def lifespan(app: FastAPI):
|
||||
# DB 풀 초기화
|
||||
await init_db_pool()
|
||||
logger.info("✓ DB 풀 초기화 완료")
|
||||
if ADMIN_EMAIL:
|
||||
pool = get_db_pool()
|
||||
async with pool.acquire() as connection, connection.cursor() as cursor:
|
||||
await cursor.execute(
|
||||
"UPDATE users SET role = 'SYSTEM_ADMIN' WHERE email = %s", (ADMIN_EMAIL.lower(),)
|
||||
)
|
||||
await connection.commit()
|
||||
cleanup_task = asyncio.create_task(cleanup_expired_sessions())
|
||||
|
||||
yield
|
||||
|
||||
# 종료
|
||||
logger.info("앱 종료 중...")
|
||||
cleanup_task.cancel()
|
||||
await close_db_pool()
|
||||
logger.info("✓ DB 풀 종료 완료")
|
||||
|
||||
@@ -197,10 +229,15 @@ async def health():
|
||||
# app.include_router(a01_router, prefix="/api/a01", tags=["A01_Home"])
|
||||
#
|
||||
# 나중에 각 페이지별 라우터가 구현되면 여기에 등록.
|
||||
app.include_router(b03_file_input_router)
|
||||
app.include_router(b04_surface_router)
|
||||
app.include_router(b05_route_router)
|
||||
app.include_router(b06_section_router)
|
||||
app.include_router(a06_login_router)
|
||||
app.include_router(a07_register_router)
|
||||
app.include_router(a08_support_router)
|
||||
app.include_router(a09_security_router)
|
||||
protected = [Depends(verify_session)]
|
||||
app.include_router(b03_file_input_router, dependencies=protected)
|
||||
app.include_router(b04_surface_router, dependencies=protected)
|
||||
app.include_router(b05_route_router, dependencies=protected)
|
||||
app.include_router(b06_section_router, dependencies=protected)
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# 앱 실행
|
||||
|
||||
Reference in New Issue
Block a user