|
1 | 1 | use forge_app::OAuthHttpProvider; |
2 | 2 | use forge_domain::{AuthCodeParams, OAuthConfig, OAuthTokenResponse}; |
3 | | -use oauth2::{ |
4 | | - AuthorizationCode as OAuth2AuthCode, CsrfToken, PkceCodeChallenge, PkceCodeVerifier, Scope, |
5 | | -}; |
| 3 | +use oauth2::{CsrfToken, PkceCodeChallenge, Scope}; |
| 4 | +use serde::Serialize; |
6 | 5 |
|
7 | 6 | use crate::auth::util::*; |
8 | 7 |
|
9 | 8 | /// Standard RFC-compliant OAuth provider |
10 | 9 | pub struct StandardHttpProvider; |
11 | 10 |
|
| 11 | +#[derive(Debug, Serialize)] |
| 12 | +struct StandardTokenRequest<'a> { |
| 13 | + grant_type: &'static str, |
| 14 | + code: &'a str, |
| 15 | + client_id: &'a str, |
| 16 | + #[serde(skip_serializing_if = "Option::is_none")] |
| 17 | + redirect_uri: Option<&'a str>, |
| 18 | + #[serde(skip_serializing_if = "Option::is_none")] |
| 19 | + code_verifier: Option<&'a str>, |
| 20 | +} |
| 21 | + |
12 | 22 | #[async_trait::async_trait] |
13 | 23 | impl OAuthHttpProvider for StandardHttpProvider { |
14 | 24 | async fn build_auth_url(&self, config: &OAuthConfig) -> anyhow::Result<AuthCodeParams> { |
@@ -58,27 +68,33 @@ impl OAuthHttpProvider for StandardHttpProvider { |
58 | 68 | code: &str, |
59 | 69 | verifier: Option<&str>, |
60 | 70 | ) -> anyhow::Result<OAuthTokenResponse> { |
61 | | - use oauth2::{AuthUrl, ClientId, TokenUrl}; |
62 | | - |
63 | | - let mut client = |
64 | | - oauth2::basic::BasicClient::new(ClientId::new(config.client_id.to_string())) |
65 | | - .set_auth_uri(AuthUrl::new(config.auth_url.to_string())?) |
66 | | - .set_token_uri(TokenUrl::new(config.token_url.to_string())?); |
67 | | - |
68 | | - if let Some(redirect_uri) = &config.redirect_uri { |
69 | | - client = client.set_redirect_uri(oauth2::RedirectUrl::new(redirect_uri.clone())?); |
70 | | - } |
71 | | - |
72 | 71 | let http_client = self.build_http_client(config)?; |
| 72 | + let request_body = StandardTokenRequest { |
| 73 | + grant_type: "authorization_code", |
| 74 | + code, |
| 75 | + client_id: config.client_id.as_ref(), |
| 76 | + redirect_uri: config.redirect_uri.as_deref(), |
| 77 | + code_verifier: verifier, |
| 78 | + }; |
| 79 | + |
| 80 | + let response = http_client |
| 81 | + .post(config.token_url.as_str()) |
| 82 | + .header("Content-Type", "application/x-www-form-urlencoded") |
| 83 | + .header("Accept", "application/json") |
| 84 | + .body(serde_urlencoded::to_string(&request_body)?) |
| 85 | + .send() |
| 86 | + .await?; |
73 | 87 |
|
74 | | - let mut request = client.exchange_code(OAuth2AuthCode::new(code.to_string())); |
| 88 | + let status = response.status(); |
| 89 | + let body = response.text().await?; |
75 | 90 |
|
76 | | - if let Some(v) = verifier { |
77 | | - request = request.set_pkce_verifier(PkceCodeVerifier::new(v.to_string())); |
| 91 | + if !status.is_success() { |
| 92 | + anyhow::bail!("OAuth token exchange failed ({status}): {body}"); |
78 | 93 | } |
79 | 94 |
|
80 | | - let token_result = request.request_async(&http_client).await?; |
81 | | - Ok(into_domain(token_result)) |
| 95 | + // Parse the raw token payload so provider-specific fields like |
| 96 | + // `id_token` are preserved instead of being dropped by generic helpers. |
| 97 | + Ok(parse_token_response(&body)?) |
82 | 98 | } |
83 | 99 |
|
84 | 100 | /// Create HTTP client with provider-specific headers/behavior |
|
0 commit comments