Skip to content

Commit 647bc37

Browse files
authored
Merge pull request #347 from Pseudo-Lab/deploy/cert-develop
fix: cert security config
2 parents 81491dd + eecba34 commit 647bc37

5 files changed

Lines changed: 41 additions & 11 deletions

File tree

β€Žcert/backend/.env.exampleβ€Ž

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# LOG_LEVEL=INFO
2+
# ENVIRONMENT=dev
3+
4+
# CORS Origins (μ‰Όν‘œλ‘œ ꡬ뢄)
5+
CORS_ORIGINS=https://cert.pseudo-lab.com,https://dev-cert.pseudolab-devfactory.com,http://localhost:5173

β€Žcert/backend/src/constants/error_codes.pyβ€Ž

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ class ErrorCodes:
55

66
class ErrorMessages:
77
"""μ—λŸ¬ λ©”μ‹œμ§€ μƒμˆ˜"""
8-
NO_HISTORY = "수료이λ ₯이 ν™•μΈλ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. πŸ₯²\nλ””μŠ€μ½”λ“œλ₯Ό 톡해 κΉ€μ°¬λž€μ—κ²Œ λ¬Έμ˜ν•΄μ£Όμ„Έμš”."
8+
NO_HISTORY = "수료 λͺ…단에 μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. πŸ₯²\nμž…λ ₯ν•˜μ‹  이름 '{name}'(이)κ°€ λͺ…단에 μžˆλŠ”μ§€ ν™•μΈν•˜κ±°λ‚˜, μŠ€ν„°λ”” λΉŒλ” ν˜Ήμ€ μ§ˆλ¬Έκ²Œμ‹œνŒμ— λ¬Έμ˜ν•΄μ£Όμ„Έμš”."
9+
USER_DROPPED_OUT = "μŠ€ν„°λ””λ₯Ό μˆ˜λ£Œν•˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€. πŸ₯²\nμŠ€ν„°λ”” λΉŒλ” ν˜Ήμ€ μ§ˆλ¬Έκ²Œμ‹œνŒμ— λ¬Έμ˜ν•΄μ£Όμ„Έμš”."
10+
STUDY_NOT_COMPLETED = "μˆ˜λ£Œμ¦μ€ μŠ€ν„°λ””κ°€ μ™„λ£Œλœ 이후 λ°œκΈ‰ κ°€λŠ₯ν•©λ‹ˆλ‹€.\nμŠ€ν„°λ”” λΉŒλ” ν˜Ήμ€ μ§ˆλ¬Έκ²Œμ‹œνŒμ— λ¬Έμ˜ν•΄μ£Όμ„Έμš”."
11+
PROJECT_NOT_FOUND = "ν•΄λ‹Ή ν”„λ‘œμ νŠΈκ°€ κ²€μƒ‰λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.\nκΈ°μˆ˜μ™€ μŠ€ν„°λ””λͺ…을 λ‹€μ‹œ ν™•μΈν•˜κ±°λ‚˜, μŠ€ν„°λ”” λΉŒλ” ν˜Ήμ€ μ§ˆλ¬Έκ²Œμ‹œνŒμ— λ¬Έμ˜ν•΄μ£Όμ„Έμš”."
912
PIPELINE_ERROR = "λ°œκΈ‰ 처리 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€."
1013
CONTACT_INFO = "μ‹œμŠ€ν…œ μƒμ˜ 였λ₯˜λ‘œ 수료증 λ°œκΈ‰μ— μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€. πŸ₯²\nλ””μŠ€μ½”λ“œλ₯Ό 톡해 κΉ€μˆ˜ν˜„(kyopbi)μ—κ²Œ λ¬Έμ˜ν•΄μ£Όμ„Έμš”"
1114

β€Žcert/backend/src/main.pyβ€Ž

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,23 @@ def configure_logging() -> None:
4040
# Access log middleware
4141
app.middleware("http")(access_log_middleware)
4242

43-
# CORS 미듀웨어 μ„€μ •
44-
origins = os.getenv("CORS_ORIGINS", "").split(",")
43+
# CORS configuration
44+
# Load allowed origins from CORS_ORIGINS environment variable (comma-separated)
45+
cors_origins_str = os.getenv("CORS_ORIGINS", "")
46+
if cors_origins_str:
47+
origins = [origin.strip() for origin in cors_origins_str.split(",") if origin.strip()]
48+
else:
49+
# Default origins for local development and known production/dev domains
50+
origins = [
51+
"http://localhost:3000",
52+
"http://localhost:5173",
53+
"https://cert.pseudo-lab.com",
54+
"https://dev-cert.pseudolab-devfactory.com",
55+
]
56+
4557
app.add_middleware(
4658
CORSMiddleware,
47-
allow_origins=["*"],
59+
allow_origins=origins,
4860
allow_credentials=True,
4961
allow_methods=["*"],
5062
allow_headers=["*"],

β€Žcert/backend/src/services/certificate_service.pyβ€Ž

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,15 @@ async def _create_new_certificate(
589589
)
590590
)
591591

592+
except NotEligibleError as e:
593+
# 수료 λŒ€μƒ μ•„λ‹˜ (λͺ…단에 μ—†κ±°λ‚˜ μ΄νƒˆμž λ“±)
594+
logger.warning(f"수료 λŒ€μƒ μ•„λ‹˜: {str(e)}")
595+
if request_id:
596+
await notion_client.update_certificate_status(
597+
page_id=request_id,
598+
status=CertificateStatus.NOT_ELIGIBLE
599+
)
600+
raise e
592601
except Exception as e:
593602
# μ‹œμŠ€ν…œ 였λ₯˜
594603
logger.exception("μ‹ κ·œ 수료증 λ°œκΈ‰ 쀑 μ‹œμŠ€ν…œ 였λ₯˜")

β€Žcert/backend/src/utils/notion_client.pyβ€Ž

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import aiohttp
77
from typing import Optional, Dict, Any, List
88

9-
from ..constants.error_codes import NotEligibleError, ResponseStatus
9+
from ..constants.error_codes import NotEligibleError, ResponseStatus, ErrorMessages
1010
from ..models.certificate import CertificateStatus
1111
from ..models.project import Project, SeasonGroup, ProjectsBySeasonResponse
1212

@@ -156,14 +156,17 @@ async def verify_user_participation(
156156
user_role = "BUILDER"
157157
elif user_name in runner_names:
158158
user_role = "RUNNER"
159+
# '수료자' ν•„λ“œμ— 이름이 ν¬ν•¨λ˜μ–΄ μžˆλŠ”μ§€ 확인
160+
elif any(user_name in c for c in completer_names):
161+
user_role = "RUNNER"
159162

160163
# 2. μ‚¬μš©μžκ°€ μ΄νƒˆμžμ— μžˆλŠ”μ§€ 확인
161164
if user_name in dropout_names:
162-
raise NotEligibleError(f"수료 λͺ…단에 μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. πŸ₯²\nλ””μŠ€μ½”λ“œλ₯Ό 톡해 μ§ˆλ¬Έκ²Œμ‹œνŒμ— λ¬Έμ˜ν•΄μ£Όμ„Έμš”.")
165+
raise NotEligibleError(ErrorMessages.USER_DROPPED_OUT)
163166

164167
# 3. μ‚¬μš©μžκ°€ μ°Έμ—¬μž λͺ©λ‘μ— μžˆλŠ”μ§€ 확인
165168
if user_role is None:
166-
raise NotEligibleError(f"수료 λͺ…단에 μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. πŸ₯²\nλ””μŠ€μ½”λ“œλ₯Ό 톡해 μ§ˆλ¬Έκ²Œμ‹œνŒμ— λ¬Έμ˜ν•΄μ£Όμ„Έμš”.")
169+
raise NotEligibleError(ErrorMessages.NO_HISTORY.format(name=user_name))
167170

168171
study_status = properties.get("단계", {}).get("select", {})
169172
period_raw = project.get("properties", {}).get("κΈ°κ°„", {}).get("date", {}) or {}
@@ -174,9 +177,7 @@ async def verify_user_participation(
174177
)
175178

176179
if study_status.get("name") != "μ™„λ£Œ":
177-
raise NotEligibleError(
178-
"μˆ˜λ£Œμ¦μ€ μŠ€ν„°λ””κ°€ μ™„λ£Œλœ 이후 λ°œκΈ‰ κ°€λŠ₯ν•©λ‹ˆλ‹€.\nλ””μŠ€μ½”λ“œλ₯Ό 톡해 μ§ˆλ¬Έκ²Œμ‹œνŒμ— λ¬Έμ˜ν•΄μ£Όμ„Έμš”."
179-
)
180+
raise NotEligibleError(ErrorMessages.STUDY_NOT_COMPLETED)
180181

181182
fallback_period = self.default_periods.get(str(season), {})
182183
raw_start = period_raw.get("start")
@@ -273,7 +274,7 @@ async def verify_user_participation(
273274
"course_name": course_name,
274275
},
275276
)
276-
raise Exception("ν•΄λ‹Ή ν”„λ‘œμ νŠΈκ°€ κ²€μƒ‰λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. \nλ””μŠ€μ½”λ“œλ₯Ό 톡해 μ§ˆλ¬Έκ²Œμ‹œνŒμ— λ¬Έμ˜ν•΄μ£Όμ„Έμš”.")
277+
raise NotEligibleError(ErrorMessages.PROJECT_NOT_FOUND)
277278
except Exception as e:
278279
raise e
279280

0 commit comments

Comments
Β (0)