From 5fe31aeffa38f132a479cd0ae5262e6cd590700a Mon Sep 17 00:00:00 2001 From: bruno Date: Mon, 13 Jul 2026 15:59:39 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20redirect=5Furi=20dynamique=20bas=C3=A9?= =?UTF-8?q?=20sur=20Host=20header?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Avant: oauth_redirect_uri codé en dur à localhost:8080 → impossible d'utiliser l'OAuth depuis une autre machine du réseau Après: le redirect_uri est construit à partir du header Host de la requête → localhost:8080 → http://localhost:8080/auth/callback → 192.168.30.101:8080 → http://192.168.30.101:8080/auth/callback ⚠️ L'utilisateur doit ajouter les 2 URIs dans Gitea OAuth App settings. --- app/auth/providers.py | 18 +++++++++--------- app/routers/auth.py | 11 ++++++++--- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/app/auth/providers.py b/app/auth/providers.py index 9cd457d..609f4d0 100644 --- a/app/auth/providers.py +++ b/app/auth/providers.py @@ -22,11 +22,11 @@ class OAuthProvider(ABC): """Whether this provider is configured.""" @abstractmethod - def get_authorize_url(self, state: str) -> str: + def get_authorize_url(self, state: str, redirect_uri: str | None = None) -> str: """Build the authorization URL.""" @abstractmethod - async def exchange_code(self, code: str) -> dict | None: + async def exchange_code(self, code: str, redirect_uri: str | None = None) -> dict | None: """Exchange authorization code for access token.""" @abstractmethod @@ -53,25 +53,25 @@ class GiteaProvider(OAuthProvider): def is_enabled(self) -> bool: return bool(self.client_id and self.client_secret) - def get_authorize_url(self, state: str) -> str: + def get_authorize_url(self, state: str, redirect_uri: str | None = None) -> str: return ( f"{self.base}/login/oauth/authorize?" + urlencode({ "client_id": self.client_id, - "redirect_uri": self.redirect_uri, + "redirect_uri": redirect_uri or self.redirect_uri, "response_type": "code", "state": state, }) ) - async def exchange_code(self, code: str) -> dict | None: + async def exchange_code(self, code: str, redirect_uri: str | None = None) -> dict | None: url = f"{self.base}/login/oauth/access_token" data = { "client_id": self.client_id, "client_secret": self.client_secret, "code": code, "grant_type": "authorization_code", - "redirect_uri": self.redirect_uri, + "redirect_uri": redirect_uri or self.redirect_uri, } async with httpx.AsyncClient(timeout=15) as client: r = await client.post(url, json=data, headers={"Accept": "application/json"}) @@ -143,18 +143,18 @@ class GitHubProvider(OAuthProvider): def is_enabled(self) -> bool: return bool(self.client_id and self.client_secret) - def get_authorize_url(self, state: str) -> str: + def get_authorize_url(self, state: str, redirect_uri: str | None = None) -> str: return ( f"{self.authorize_url}?" + urlencode({ "client_id": self.client_id, - "redirect_uri": self.redirect_uri, + "redirect_uri": redirect_uri or self.redirect_uri, "scope": "repo,user", "state": state, }) ) - async def exchange_code(self, code: str) -> dict | None: + async def exchange_code(self, code: str, redirect_uri: str | None = None) -> dict | None: async with httpx.AsyncClient(timeout=15) as client: r = await client.post( self.token_url, diff --git a/app/routers/auth.py b/app/routers/auth.py index 2ca238a..67e70f8 100644 --- a/app/routers/auth.py +++ b/app/routers/auth.py @@ -115,7 +115,10 @@ async def login(request: Request, provider: str = Query("gitea")): state = secrets.token_hex(32) request.session["oauth_state"] = state request.session["oauth_provider"] = provider - auth_url = oauth_provider.get_authorize_url(state) + # Dynamic redirect URI based on incoming Host header + host = request.headers.get("host", "localhost:8080") + dynamic_redirect_uri = f"http://{host}/auth/callback" + auth_url = oauth_provider.get_authorize_url(state, redirect_uri=dynamic_redirect_uri) return RedirectResponse(url=auth_url, status_code=302) @@ -237,8 +240,10 @@ async def callback( if not oauth_provider: return HTMLResponse(f"

Unknown provider: {provider_name}

", status_code=400) - # Exchange code for token - token_data = await oauth_provider.exchange_code(code) + # Exchange code for token — use dynamic redirect URI matching the authorize step + host = request.headers.get("host", "localhost:8080") + dynamic_redirect_uri = f"http://{host}/auth/callback" + token_data = await oauth_provider.exchange_code(code, redirect_uri=dynamic_redirect_uri) if not token_data: return HTMLResponse("

Token exchange failed

", status_code=400)