aaa
This commit is contained in:
parent
f498a3e308
commit
1c94b958f3
@ -74,15 +74,31 @@ settings = get_api_settings()
|
|||||||
# --- Constants derived from settings ---
|
# --- Constants derived from settings ---
|
||||||
DISCORD_API_BASE_URL = "https://discord.com/api/v10"
|
DISCORD_API_BASE_URL = "https://discord.com/api/v10"
|
||||||
DISCORD_API_ENDPOINT = DISCORD_API_BASE_URL # Alias for backward compatibility
|
DISCORD_API_ENDPOINT = DISCORD_API_BASE_URL # Alias for backward compatibility
|
||||||
DISCORD_AUTH_URL = (
|
|
||||||
|
# Define dashboard-specific redirect URI
|
||||||
|
DASHBOARD_REDIRECT_URI = f"{settings.DISCORD_REDIRECT_URI.split('/api')[0]}/dashboard/api/auth/callback"
|
||||||
|
|
||||||
|
# We'll generate the full auth URL with PKCE parameters in the dashboard_login function
|
||||||
|
# This is just a base URL without the PKCE parameters
|
||||||
|
DISCORD_AUTH_BASE_URL = (
|
||||||
f"https://discord.com/api/oauth2/authorize?client_id={settings.DISCORD_CLIENT_ID}"
|
f"https://discord.com/api/oauth2/authorize?client_id={settings.DISCORD_CLIENT_ID}"
|
||||||
f"&redirect_uri={settings.DISCORD_REDIRECT_URI}&response_type=code&scope=identify guilds"
|
f"&redirect_uri={settings.DISCORD_REDIRECT_URI}&response_type=code&scope=identify guilds"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Dashboard-specific auth base URL
|
||||||
|
DASHBOARD_AUTH_BASE_URL = (
|
||||||
|
f"https://discord.com/api/oauth2/authorize?client_id={settings.DISCORD_CLIENT_ID}"
|
||||||
|
f"&redirect_uri={DASHBOARD_REDIRECT_URI}&response_type=code&scope=identify guilds"
|
||||||
|
)
|
||||||
|
|
||||||
DISCORD_TOKEN_URL = f"{DISCORD_API_BASE_URL}/oauth2/token"
|
DISCORD_TOKEN_URL = f"{DISCORD_API_BASE_URL}/oauth2/token"
|
||||||
DISCORD_USER_URL = f"{DISCORD_API_BASE_URL}/users/@me"
|
DISCORD_USER_URL = f"{DISCORD_API_BASE_URL}/users/@me"
|
||||||
DISCORD_USER_GUILDS_URL = f"{DISCORD_API_BASE_URL}/users/@me/guilds"
|
DISCORD_USER_GUILDS_URL = f"{DISCORD_API_BASE_URL}/users/@me/guilds"
|
||||||
DISCORD_REDIRECT_URI = settings.DISCORD_REDIRECT_URI # Make it accessible directly
|
DISCORD_REDIRECT_URI = settings.DISCORD_REDIRECT_URI # Make it accessible directly
|
||||||
|
|
||||||
|
# For backward compatibility, keep DISCORD_AUTH_URL but it will be replaced in the dashboard_login function
|
||||||
|
DISCORD_AUTH_URL = DISCORD_AUTH_BASE_URL
|
||||||
|
|
||||||
|
|
||||||
# --- Gurt Stats Storage (IPC) ---
|
# --- Gurt Stats Storage (IPC) ---
|
||||||
latest_gurt_stats: Optional[Dict[str, Any]] = None
|
latest_gurt_stats: Optional[Dict[str, Any]] = None
|
||||||
@ -521,13 +537,41 @@ async def verify_dashboard_guild_admin(guild_id: int, current_user: dict = Depen
|
|||||||
# --- Dashboard Authentication Routes ---
|
# --- Dashboard Authentication Routes ---
|
||||||
@dashboard_api_app.get("/auth/login", tags=["Dashboard Authentication"])
|
@dashboard_api_app.get("/auth/login", tags=["Dashboard Authentication"])
|
||||||
async def dashboard_login():
|
async def dashboard_login():
|
||||||
"""Redirects the user to Discord for OAuth2 authorization (Dashboard Flow)."""
|
"""Redirects the user to Discord for OAuth2 authorization (Dashboard Flow) with PKCE."""
|
||||||
# Uses constants derived from settings loaded at the top
|
import secrets
|
||||||
log.info(f"Dashboard: Redirecting user to Discord auth URL: {DISCORD_AUTH_URL}")
|
import hashlib
|
||||||
return RedirectResponse(url=DISCORD_AUTH_URL, status_code=status.HTTP_307_TEMPORARY_REDIRECT)
|
import base64
|
||||||
|
|
||||||
|
# Generate a random state for CSRF protection
|
||||||
|
state = secrets.token_urlsafe(32)
|
||||||
|
|
||||||
|
# Generate a code verifier for PKCE
|
||||||
|
code_verifier = secrets.token_urlsafe(64)
|
||||||
|
|
||||||
|
# Generate a code challenge from the code verifier
|
||||||
|
code_challenge_bytes = hashlib.sha256(code_verifier.encode()).digest()
|
||||||
|
code_challenge = base64.urlsafe_b64encode(code_challenge_bytes).decode().rstrip("=")
|
||||||
|
|
||||||
|
# Store the code verifier for later use
|
||||||
|
code_verifier_store.store_code_verifier(state, code_verifier)
|
||||||
|
|
||||||
|
# Build the authorization URL with PKCE parameters using the dashboard-specific redirect URI
|
||||||
|
auth_url = (
|
||||||
|
f"{DASHBOARD_AUTH_BASE_URL}"
|
||||||
|
f"&state={state}"
|
||||||
|
f"&code_challenge={code_challenge}"
|
||||||
|
f"&code_challenge_method=S256"
|
||||||
|
f"&prompt=consent"
|
||||||
|
)
|
||||||
|
|
||||||
|
log.info(f"Dashboard: Redirecting user to Discord auth URL with PKCE: {auth_url}")
|
||||||
|
log.info(f"Dashboard: Using redirect URI: {DASHBOARD_REDIRECT_URI}")
|
||||||
|
log.info(f"Dashboard: Stored code verifier for state {state}: {code_verifier[:10]}...")
|
||||||
|
|
||||||
|
return RedirectResponse(url=auth_url, status_code=status.HTTP_307_TEMPORARY_REDIRECT)
|
||||||
|
|
||||||
@dashboard_api_app.get("/auth/callback", tags=["Dashboard Authentication"])
|
@dashboard_api_app.get("/auth/callback", tags=["Dashboard Authentication"])
|
||||||
async def dashboard_auth_callback(request: Request, code: str | None = None, error: str | None = None):
|
async def dashboard_auth_callback(request: Request, code: str | None = None, state: str | None = None, error: str | None = None):
|
||||||
"""Handles the callback from Discord after authorization (Dashboard Flow)."""
|
"""Handles the callback from Discord after authorization (Dashboard Flow)."""
|
||||||
global http_session # Use the global aiohttp session
|
global http_session # Use the global aiohttp session
|
||||||
if error:
|
if error:
|
||||||
@ -538,24 +582,45 @@ async def dashboard_auth_callback(request: Request, code: str | None = None, err
|
|||||||
log.error("Dashboard: Discord OAuth callback missing code.")
|
log.error("Dashboard: Discord OAuth callback missing code.")
|
||||||
return RedirectResponse(url="/dashboard?error=missing_code")
|
return RedirectResponse(url="/dashboard?error=missing_code")
|
||||||
|
|
||||||
|
if not state:
|
||||||
|
log.error("Dashboard: Discord OAuth callback missing state parameter.")
|
||||||
|
return RedirectResponse(url="/dashboard?error=missing_state")
|
||||||
|
|
||||||
if not http_session:
|
if not http_session:
|
||||||
log.error("Dashboard: aiohttp session not initialized.")
|
log.error("Dashboard: aiohttp session not initialized.")
|
||||||
raise HTTPException(status_code=500, detail="Internal server error: HTTP session not ready.")
|
raise HTTPException(status_code=500, detail="Internal server error: HTTP session not ready.")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 1. Exchange code for access token
|
# Get the code verifier from the store
|
||||||
|
code_verifier = code_verifier_store.get_code_verifier(state)
|
||||||
|
if not code_verifier:
|
||||||
|
log.error(f"Dashboard: No code_verifier found for state {state}")
|
||||||
|
return RedirectResponse(url="/dashboard?error=missing_code_verifier")
|
||||||
|
|
||||||
|
log.info(f"Dashboard: Found code_verifier for state {state}: {code_verifier[:10]}...")
|
||||||
|
|
||||||
|
# Remove the code verifier from the store after retrieving it
|
||||||
|
code_verifier_store.remove_code_verifier(state)
|
||||||
|
|
||||||
|
# 1. Exchange code for access token with PKCE
|
||||||
token_data = {
|
token_data = {
|
||||||
'client_id': settings.DISCORD_CLIENT_ID,
|
'client_id': settings.DISCORD_CLIENT_ID,
|
||||||
'client_secret': settings.DISCORD_CLIENT_SECRET,
|
|
||||||
'grant_type': 'authorization_code',
|
'grant_type': 'authorization_code',
|
||||||
'code': code,
|
'code': code,
|
||||||
'redirect_uri': settings.DISCORD_REDIRECT_URI # Must match exactly
|
'redirect_uri': DASHBOARD_REDIRECT_URI, # Must match exactly what was used in the auth request
|
||||||
|
'code_verifier': code_verifier # Add the code verifier for PKCE
|
||||||
}
|
}
|
||||||
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
|
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
|
||||||
|
|
||||||
log.debug(f"Dashboard: Exchanging code for token at {DISCORD_TOKEN_URL}")
|
log.debug(f"Dashboard: Exchanging code for token at {DISCORD_TOKEN_URL} with PKCE")
|
||||||
|
log.debug(f"Dashboard: Token exchange data: {token_data}")
|
||||||
|
|
||||||
async with http_session.post(DISCORD_TOKEN_URL, data=token_data, headers=headers) as resp:
|
async with http_session.post(DISCORD_TOKEN_URL, data=token_data, headers=headers) as resp:
|
||||||
resp.raise_for_status()
|
if resp.status != 200:
|
||||||
|
error_text = await resp.text()
|
||||||
|
log.error(f"Dashboard: Failed to exchange code: {error_text}")
|
||||||
|
return RedirectResponse(url=f"/dashboard?error=token_exchange_failed&details={error_text}")
|
||||||
|
|
||||||
token_response = await resp.json()
|
token_response = await resp.json()
|
||||||
access_token = token_response.get('access_token')
|
access_token = token_response.get('access_token')
|
||||||
log.debug("Dashboard: Token exchange successful.")
|
log.debug("Dashboard: Token exchange successful.")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user