feat: redirect_uri dynamique basé sur Host header
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.
This commit is contained in:
@@ -22,11 +22,11 @@ class OAuthProvider(ABC):
|
|||||||
"""Whether this provider is configured."""
|
"""Whether this provider is configured."""
|
||||||
|
|
||||||
@abstractmethod
|
@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."""
|
"""Build the authorization URL."""
|
||||||
|
|
||||||
@abstractmethod
|
@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."""
|
"""Exchange authorization code for access token."""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
@@ -53,25 +53,25 @@ class GiteaProvider(OAuthProvider):
|
|||||||
def is_enabled(self) -> bool:
|
def is_enabled(self) -> bool:
|
||||||
return bool(self.client_id and self.client_secret)
|
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 (
|
return (
|
||||||
f"{self.base}/login/oauth/authorize?"
|
f"{self.base}/login/oauth/authorize?"
|
||||||
+ urlencode({
|
+ urlencode({
|
||||||
"client_id": self.client_id,
|
"client_id": self.client_id,
|
||||||
"redirect_uri": self.redirect_uri,
|
"redirect_uri": redirect_uri or self.redirect_uri,
|
||||||
"response_type": "code",
|
"response_type": "code",
|
||||||
"state": state,
|
"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"
|
url = f"{self.base}/login/oauth/access_token"
|
||||||
data = {
|
data = {
|
||||||
"client_id": self.client_id,
|
"client_id": self.client_id,
|
||||||
"client_secret": self.client_secret,
|
"client_secret": self.client_secret,
|
||||||
"code": code,
|
"code": code,
|
||||||
"grant_type": "authorization_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:
|
async with httpx.AsyncClient(timeout=15) as client:
|
||||||
r = await client.post(url, json=data, headers={"Accept": "application/json"})
|
r = await client.post(url, json=data, headers={"Accept": "application/json"})
|
||||||
@@ -143,18 +143,18 @@ class GitHubProvider(OAuthProvider):
|
|||||||
def is_enabled(self) -> bool:
|
def is_enabled(self) -> bool:
|
||||||
return bool(self.client_id and self.client_secret)
|
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 (
|
return (
|
||||||
f"{self.authorize_url}?"
|
f"{self.authorize_url}?"
|
||||||
+ urlencode({
|
+ urlencode({
|
||||||
"client_id": self.client_id,
|
"client_id": self.client_id,
|
||||||
"redirect_uri": self.redirect_uri,
|
"redirect_uri": redirect_uri or self.redirect_uri,
|
||||||
"scope": "repo,user",
|
"scope": "repo,user",
|
||||||
"state": state,
|
"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:
|
async with httpx.AsyncClient(timeout=15) as client:
|
||||||
r = await client.post(
|
r = await client.post(
|
||||||
self.token_url,
|
self.token_url,
|
||||||
|
|||||||
+8
-3
@@ -115,7 +115,10 @@ async def login(request: Request, provider: str = Query("gitea")):
|
|||||||
state = secrets.token_hex(32)
|
state = secrets.token_hex(32)
|
||||||
request.session["oauth_state"] = state
|
request.session["oauth_state"] = state
|
||||||
request.session["oauth_provider"] = provider
|
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)
|
return RedirectResponse(url=auth_url, status_code=302)
|
||||||
|
|
||||||
|
|
||||||
@@ -237,8 +240,10 @@ async def callback(
|
|||||||
if not oauth_provider:
|
if not oauth_provider:
|
||||||
return HTMLResponse(f"<h1>Unknown provider: {provider_name}</h1>", status_code=400)
|
return HTMLResponse(f"<h1>Unknown provider: {provider_name}</h1>", status_code=400)
|
||||||
|
|
||||||
# Exchange code for token
|
# Exchange code for token — use dynamic redirect URI matching the authorize step
|
||||||
token_data = await oauth_provider.exchange_code(code)
|
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:
|
if not token_data:
|
||||||
return HTMLResponse("<h1>Token exchange failed</h1>", status_code=400)
|
return HTMLResponse("<h1>Token exchange failed</h1>", status_code=400)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user