Skip to content

Commit 5bec659

Browse files
committed
feat(cert): open Swagger in dev, protect in prod
1 parent 141c46b commit 5bec659

2 files changed

Lines changed: 66 additions & 3 deletions

File tree

getcloser/.env.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,7 @@ DB_DATABASE=app_db
1212
APP_HOST=localhost
1313
TEAM_SIZE=1
1414
PENDING_TIMEOUT_MINUTES=1
15+
16+
# Swagger Credentials
17+
SWAGGER_USER=your_swagger_username
18+
SWAGGER_PASSWORD=your_swagger_password

getcloser/backend/app/main.py

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1-
from fastapi import FastAPI
1+
from fastapi import Depends, FastAPI, HTTPException, status
22
from fastapi.middleware.cors import CORSMiddleware
3+
from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html
4+
from fastapi.openapi.utils import get_openapi
5+
from fastapi.responses import JSONResponse
6+
from fastapi.security import HTTPBasic, HTTPBasicCredentials
37
from dotenv import load_dotenv
48
import os
9+
import secrets
510

611
from core.database import engine, Base
712
from routers import test_db, admin
@@ -17,12 +22,14 @@
1722
Base.metadata.create_all(bind=engine)
1823

1924
# FastAPI 앱 생성
25+
is_dev = os.getenv("ENV") == "development"
2026
app = FastAPI(
2127
title="Devfactory 친해지길바라",
2228
description="Devfactory 친해지길바라 API 서버",
2329
version="1.0.0",
24-
docs_url="/docs",
25-
redoc_url="/redoc",
30+
docs_url="/docs" if is_dev else None,
31+
redoc_url="/redoc" if is_dev else None,
32+
openapi_url="/openapi.json" if is_dev else None,
2633
)
2734

2835

@@ -41,6 +48,58 @@
4148
app.include_router(test_db.test_router, prefix="/api/v1")
4249
app.include_router(admin.admin_router, prefix="/api/v1")
4350

51+
security = HTTPBasic(auto_error=False)
52+
53+
def verify_docs_credentials(credentials: HTTPBasicCredentials | None = Depends(security)) -> None:
54+
if is_dev:
55+
return
56+
57+
expected_user = os.getenv("SWAGGER_USER")
58+
expected_password = os.getenv("SWAGGER_PASSWORD")
59+
60+
# If credentials aren't configured, keep docs closed by default.
61+
if not expected_user or not expected_password:
62+
raise HTTPException(
63+
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
64+
detail="Swagger credentials are not configured.",
65+
)
66+
67+
if credentials is None:
68+
raise HTTPException(
69+
status_code=status.HTTP_401_UNAUTHORIZED,
70+
detail="Missing credentials",
71+
headers={"WWW-Authenticate": "Basic"},
72+
)
73+
74+
is_user_ok = secrets.compare_digest(credentials.username, expected_user)
75+
is_password_ok = secrets.compare_digest(credentials.password, expected_password)
76+
if not (is_user_ok and is_password_ok):
77+
raise HTTPException(
78+
status_code=status.HTTP_401_UNAUTHORIZED,
79+
detail="Invalid credentials",
80+
headers={"WWW-Authenticate": "Basic"},
81+
)
82+
83+
if not is_dev:
84+
@app.get("/docs", include_in_schema=False)
85+
def get_swagger_docs(_: None = Depends(verify_docs_credentials)):
86+
return get_swagger_ui_html(openapi_url="/openapi.json", title="API Docs")
87+
88+
@app.get("/redoc", include_in_schema=False)
89+
def get_redoc_docs(_: None = Depends(verify_docs_credentials)):
90+
return get_redoc_html(openapi_url="/openapi.json", title="API Docs")
91+
92+
@app.get("/openapi.json", include_in_schema=False)
93+
def openapi_json(_: None = Depends(verify_docs_credentials)):
94+
return JSONResponse(
95+
get_openapi(
96+
title=app.title,
97+
version=app.version,
98+
description=app.description,
99+
routes=app.routes,
100+
)
101+
)
102+
44103

45104
@app.get("/")
46105
async def read_root():

0 commit comments

Comments
 (0)