Fix OAuth token exchange: use internal URL, add error logging
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m50s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m50s
The server-to-server token exchange was hitting the external URL (https://account.rose-ash.com/...) which can fail from inside Docker due to DNS/hairpin NAT. Now uses INTERNAL_URL_ACCOUNT (already set in both docker-compose files) for the POST. Adds logging at all three failure points so silent redirects are diagnosable. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -64,6 +64,11 @@ class Settings:
|
|||||||
default_factory=lambda: os.environ.get("SECRET_KEY", "change-me-in-production")
|
default_factory=lambda: os.environ.get("SECRET_KEY", "change-me-in-production")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Internal account URL for server-to-server token exchange (avoids external DNS/TLS)
|
||||||
|
internal_account_url: str = field(
|
||||||
|
default_factory=lambda: os.environ.get("INTERNAL_URL_ACCOUNT", "")
|
||||||
|
)
|
||||||
|
|
||||||
# GPU/Streaming settings
|
# GPU/Streaming settings
|
||||||
streaming_gpu_persist: bool = field(
|
streaming_gpu_persist: bool = field(
|
||||||
default_factory=lambda: os.environ.get("STREAMING_GPU_PERSIST", "0") == "1"
|
default_factory=lambda: os.environ.get("STREAMING_GPU_PERSIST", "0") == "1"
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ GET /auth/callback — exchange code for user info, set session cookie
|
|||||||
GET /auth/logout — clear cookie, redirect through account SSO logout
|
GET /auth/logout — clear cookie, redirect through account SSO logout
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
import secrets
|
import secrets
|
||||||
import time
|
import time
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ from artdag_common.middleware.auth import UserContext, set_auth_cookie, clear_au
|
|||||||
|
|
||||||
from ..config import settings
|
from ..config import settings
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
_signer = None
|
_signer = None
|
||||||
@@ -119,24 +121,32 @@ async def callback(request: Request):
|
|||||||
return RedirectResponse(url="/", status_code=302)
|
return RedirectResponse(url="/", status_code=302)
|
||||||
|
|
||||||
# Exchange code for user info via account's token endpoint
|
# Exchange code for user info via account's token endpoint
|
||||||
|
# Prefer internal URL (Docker overlay) to avoid external DNS/TLS issues
|
||||||
|
token_url = settings.oauth_token_url
|
||||||
|
if settings.internal_account_url:
|
||||||
|
token_url = f"{settings.internal_account_url.rstrip('/')}/auth/oauth/token"
|
||||||
|
|
||||||
async with httpx.AsyncClient(timeout=10) as client:
|
async with httpx.AsyncClient(timeout=10) as client:
|
||||||
try:
|
try:
|
||||||
resp = await client.post(
|
resp = await client.post(
|
||||||
settings.oauth_token_url,
|
token_url,
|
||||||
json={
|
json={
|
||||||
"code": code,
|
"code": code,
|
||||||
"client_id": settings.oauth_client_id,
|
"client_id": settings.oauth_client_id,
|
||||||
"redirect_uri": settings.oauth_redirect_uri,
|
"redirect_uri": settings.oauth_redirect_uri,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
except httpx.HTTPError:
|
except httpx.HTTPError as exc:
|
||||||
|
logger.error("OAuth token exchange failed: %s %s", type(exc).__name__, exc)
|
||||||
return RedirectResponse(url="/", status_code=302)
|
return RedirectResponse(url="/", status_code=302)
|
||||||
|
|
||||||
if resp.status_code != 200:
|
if resp.status_code != 200:
|
||||||
|
logger.error("OAuth token exchange returned %s: %s", resp.status_code, resp.text[:200])
|
||||||
return RedirectResponse(url="/", status_code=302)
|
return RedirectResponse(url="/", status_code=302)
|
||||||
|
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
if "error" in data:
|
if "error" in data:
|
||||||
|
logger.error("OAuth token exchange error: %s", data["error"])
|
||||||
return RedirectResponse(url="/", status_code=302)
|
return RedirectResponse(url="/", status_code=302)
|
||||||
|
|
||||||
# Map OAuth response to artdag UserContext
|
# Map OAuth response to artdag UserContext
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ class Settings:
|
|||||||
oauth_logout_url: str = os.environ.get("OAUTH_LOGOUT_URL", "https://account.rose-ash.com/auth/sso-logout/")
|
oauth_logout_url: str = os.environ.get("OAUTH_LOGOUT_URL", "https://account.rose-ash.com/auth/sso-logout/")
|
||||||
secret_key: str = os.environ.get("SECRET_KEY", "change-me-in-production")
|
secret_key: str = os.environ.get("SECRET_KEY", "change-me-in-production")
|
||||||
|
|
||||||
|
# Internal account URL for server-to-server token exchange (avoids external DNS/TLS)
|
||||||
|
internal_account_url: str = os.environ.get("INTERNAL_URL_ACCOUNT", "")
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
# Parse L1 servers
|
# Parse L1 servers
|
||||||
l1_str = os.environ.get("L1_SERVERS", "https://celery-artdag.rose-ash.com")
|
l1_str = os.environ.get("L1_SERVERS", "https://celery-artdag.rose-ash.com")
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ GET /auth/callback — exchange code for user info, set session cookie
|
|||||||
GET /auth/logout — clear cookie, redirect through account SSO logout
|
GET /auth/logout — clear cookie, redirect through account SSO logout
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
import secrets
|
import secrets
|
||||||
import time
|
import time
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ from artdag_common.middleware.auth import UserContext, set_auth_cookie, clear_au
|
|||||||
|
|
||||||
from ..config import settings
|
from ..config import settings
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
_signer = None
|
_signer = None
|
||||||
@@ -119,24 +121,32 @@ async def callback(request: Request):
|
|||||||
return RedirectResponse(url="/", status_code=302)
|
return RedirectResponse(url="/", status_code=302)
|
||||||
|
|
||||||
# Exchange code for user info via account's token endpoint
|
# Exchange code for user info via account's token endpoint
|
||||||
|
# Prefer internal URL (Docker overlay) to avoid external DNS/TLS issues
|
||||||
|
token_url = settings.oauth_token_url
|
||||||
|
if settings.internal_account_url:
|
||||||
|
token_url = f"{settings.internal_account_url.rstrip('/')}/auth/oauth/token"
|
||||||
|
|
||||||
async with httpx.AsyncClient(timeout=10) as client:
|
async with httpx.AsyncClient(timeout=10) as client:
|
||||||
try:
|
try:
|
||||||
resp = await client.post(
|
resp = await client.post(
|
||||||
settings.oauth_token_url,
|
token_url,
|
||||||
json={
|
json={
|
||||||
"code": code,
|
"code": code,
|
||||||
"client_id": settings.oauth_client_id,
|
"client_id": settings.oauth_client_id,
|
||||||
"redirect_uri": settings.oauth_redirect_uri,
|
"redirect_uri": settings.oauth_redirect_uri,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
except httpx.HTTPError:
|
except httpx.HTTPError as exc:
|
||||||
|
logger.error("OAuth token exchange failed: %s %s", type(exc).__name__, exc)
|
||||||
return RedirectResponse(url="/", status_code=302)
|
return RedirectResponse(url="/", status_code=302)
|
||||||
|
|
||||||
if resp.status_code != 200:
|
if resp.status_code != 200:
|
||||||
|
logger.error("OAuth token exchange returned %s: %s", resp.status_code, resp.text[:200])
|
||||||
return RedirectResponse(url="/", status_code=302)
|
return RedirectResponse(url="/", status_code=302)
|
||||||
|
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
if "error" in data:
|
if "error" in data:
|
||||||
|
logger.error("OAuth token exchange error: %s", data["error"])
|
||||||
return RedirectResponse(url="/", status_code=302)
|
return RedirectResponse(url="/", status_code=302)
|
||||||
|
|
||||||
# Map OAuth response to artdag UserContext
|
# Map OAuth response to artdag UserContext
|
||||||
|
|||||||
Reference in New Issue
Block a user