Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions docker-compose.prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,36 @@ services:
networks:
- logit-network

# Logit MCP Server
logit-mcp:
build:
context: ../logit-mcp
dockerfile: Dockerfile
container_name: logit-mcp
restart: unless-stopped
expose:
- "8001"
env_file:
- ../logit-mcp/.env
environment:
- POSTGRES_SERVER=postgres
- POSTGRES_PORT=5432
- QDRANT_HOST=qdrant
- QDRANT_PORT=6333
depends_on:
postgres:
condition: service_healthy
qdrant:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8001/health || exit 1"]
interval: 10s
timeout: 5s
retries: 5
start_period: 15s
networks:
- logit-network

# PostgreSQL Database
postgres:
image: postgres:16-alpine
Expand Down
31 changes: 26 additions & 5 deletions scripts/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -79,20 +79,41 @@ update_caddyfile() {
# Active upstream: app-${target}

{\$DOMAIN:api.logit.ai.kr} {
# Reverse proxy to active app
reverse_proxy app-${target}:8000
reverse_proxy app-${target}:8000 {
health_uri /health
health_interval 10s
health_timeout 5s
}

encode gzip

header {
X-Content-Type-Options nosniff
X-Frame-Options DENY
Referrer-Policy strict-origin-when-cross-origin
}

log {
output stdout
format console
}
}

mcp.logit.ai.kr {
reverse_proxy logit-mcp:8001 {
health_uri /health
health_interval 10s
health_timeout 5s
}

# Enable compression
encode gzip

# Security headers
header {
X-Content-Type-Options nosniff
X-Frame-Options DENY
Referrer-Policy strict-origin-when-cross-origin
}

# Logging
log {
output stdout
format console
Expand Down
31 changes: 18 additions & 13 deletions src/auth/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,16 +131,13 @@ async def _find_or_create_user(
if existing_user:
return existing_user, False

# 2) 동일 email이 다른 provider로 이미 가입되어 있는지 확인
# 2) 동일 email로 이미 가입된 사용자가 있으면 기존 계정으로 로그인
email_user = await user_service.get_user_by_email(
session=session, email=email
)

if email_user:
raise OAuthError(
f"This email is already registered with {email_user.oauth_provider.value}. "
f"Please sign in with {email_user.oauth_provider.value}."
)
return email_user, False

try:
new_user = await create_oauth_user(
Expand Down Expand Up @@ -275,12 +272,20 @@ async def _verify_google_id_token(id_token: str) -> dict:
kid = unverified_header.get("kid")
signing_key = await _find_jwks_key(kid, _get_google_jwks, "Google")

allowed_audiences = [
aud for aud in [
settings.GOOGLE_CLIENT_ID,
settings.GOOGLE_IOS_CLIENT_ID,
settings.GOOGLE_ANDROID_CLIENT_ID,
] if aud
]

try:
decoded = jwt.decode(
id_token,
signing_key.key,
algorithms=["RS256"],
audience=settings.GOOGLE_CLIENT_ID,
audience=allowed_audiences,
issuer=["https://accounts.google.com", "accounts.google.com"],
)
except jwt.PyJWTError as e:
Expand Down Expand Up @@ -318,7 +323,7 @@ async def google_mobile_auth_flow(id_token: str, session: AsyncSession) -> dict:


async def _verify_apple_id_token(
id_token: str, platform: str, nonce: str | None = None
id_token: str, nonce: str | None = None,
) -> dict:
"""
Apple id_token을 비동기 JWKS로 검증하고 디코딩된 페이로드를 반환합니다.
Expand All @@ -330,16 +335,16 @@ async def _verify_apple_id_token(

kid = unverified_header.get("kid")
signing_key = await _find_jwks_key(kid, _get_apple_jwks, "Apple")
audience = settings.APPLE_CLIENT_ID
if platform == "app":
audience = audience[:-3]
allowed_audiences = [settings.APPLE_CLIENT_ID]
if settings.APPLE_CLIENT_ID:
allowed_audiences.append(settings.APPLE_CLIENT_ID[:-3]) # bundle id (앱용)

try:
decoded = jwt.decode(
id_token,
signing_key.key,
algorithms=["RS256"],
audience=audience,
audience=allowed_audiences,
issuer="https://appleid.apple.com",
)
except jwt.PyJWTError as e:
Expand Down Expand Up @@ -416,7 +421,7 @@ async def apple_oauth_flow(
if not id_token:
raise OAuthError("No id_token in response from Apple")

decoded = await _verify_apple_id_token(id_token, "web", nonce=nonce)
decoded = await _verify_apple_id_token(id_token, nonce=nonce)
apple_sub = decoded["sub"]
email = decoded["email"]

Expand Down Expand Up @@ -450,7 +455,7 @@ async def apple_mobile_auth_flow(
모바일용 Apple 로그인.
네이티브 SDK에서 받은 id_token을 JWKS로 검증하고 JWT 토큰 발급.
"""
decoded = await _verify_apple_id_token(id_token, "app")
decoded = await _verify_apple_id_token(id_token)
apple_sub = decoded["sub"]
email = decoded["email"]

Expand Down
2 changes: 2 additions & 0 deletions src/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ def SQLALCHEMY_DATABASE_URI(self) -> PostgresDsn:
# OAuth - Google
GOOGLE_CLIENT_ID: str | None = None
GOOGLE_CLIENT_SECRET: str | None = None
GOOGLE_IOS_CLIENT_ID: str | None = None
GOOGLE_ANDROID_CLIENT_ID: str | None = None

# OAuth - Apple
APPLE_CLIENT_ID: str | None = None
Expand Down