feat: OpenAICompatibleOAuthProvider (config-driven OAuthProvider impl)

This commit is contained in:
2026-07-20 12:55:30 -06:00
parent 68177fdb6a
commit 66bbb34d7f
7 changed files with 105 additions and 11 deletions
+2 -2
View File
@@ -25,8 +25,8 @@ impl OAuthProvider for ClaudeOAuthProvider {
"https://console.anthropic.com/oauth/code/callback"
}
fn scopes(&self) -> &str {
"org:create_api_key user:profile user:inference"
fn scopes(&self) -> String {
"org:create_api_key user:profile user:inference".to_string()
}
fn extra_authorize_params(&self) -> Vec<(&str, &str)> {
+2 -2
View File
@@ -27,8 +27,8 @@ impl OAuthProvider for GeminiOAuthProvider {
""
}
fn scopes(&self) -> &str {
"https://www.googleapis.com/auth/generative-language.peruserquota https://www.googleapis.com/auth/generative-language.retriever https://www.googleapis.com/auth/userinfo.email"
fn scopes(&self) -> String {
"https://www.googleapis.com/auth/generative-language.peruserquota https://www.googleapis.com/auth/generative-language.retriever https://www.googleapis.com/auth/userinfo.email".to_string()
}
fn client_secret(&self) -> Option<&str> {
+1
View File
@@ -4,6 +4,7 @@ mod common;
mod gemini_oauth;
mod message;
pub mod oauth;
mod openai_compatible_oauth;
mod openai_oauth;
#[macro_use]
mod macros;
+4 -3
View File
@@ -118,7 +118,7 @@ pub trait OAuthProvider: Send + Sync {
fn authorize_url(&self) -> &str;
fn token_url(&self) -> &str;
fn redirect_uri(&self) -> &str;
fn scopes(&self) -> &str;
fn scopes(&self) -> String;
fn client_secret(&self) -> Option<&str> {
None
@@ -204,7 +204,8 @@ async fn run_pkce_flow(provider: &dyn OAuthProvider, client_name: &str) -> Resul
(provider.redirect_uri().to_string(), false)
};
let encoded_scopes = urlencoding::encode(provider.scopes());
let scopes = provider.scopes();
let encoded_scopes = urlencoding::encode(&scopes);
let encoded_redirect = urlencoding::encode(&redirect_uri);
let mut authorize_url = format!(
@@ -313,7 +314,7 @@ async fn run_client_credentials_flow(
("client_id", provider.client_id()),
];
if !scopes.is_empty() {
params.push(("scope", scopes));
params.push(("scope", scopes.as_str()));
}
let request = build_token_request(&client, provider, &params);
+92
View File
@@ -0,0 +1,92 @@
use super::oauth::{OAuthConfig, OAuthFlow, OAuthProvider, TokenRequestFormat};
pub struct OpenAICompatibleOAuthProvider {
pub config: OAuthConfig,
pub client_name: String,
}
impl OAuthProvider for OpenAICompatibleOAuthProvider {
fn provider_name(&self) -> &str {
&self.client_name
}
fn client_id(&self) -> &str {
&self.config.client_id
}
fn authorize_url(&self) -> &str {
self.config.authorize_url.as_deref().unwrap_or("")
}
fn token_url(&self) -> &str {
&self.config.token_url
}
fn redirect_uri(&self) -> &str {
self.config.redirect_uri.as_deref().unwrap_or("")
}
fn scopes(&self) -> String {
self.config.scopes.join(" ")
}
fn client_secret(&self) -> Option<&str> {
self.config.client_secret.as_deref()
}
fn extra_authorize_params(&self) -> Vec<(&str, &str)> {
self.config
.extra_authorize_params
.iter()
.map(|(k, v)| (k.as_str(), v.as_str()))
.collect()
}
fn token_request_format(&self) -> TokenRequestFormat {
self.config
.token_request_format
.unwrap_or(TokenRequestFormat::FormUrlEncoded)
}
fn uses_localhost_redirect(&self) -> bool {
self.config.redirect_uri.is_none() && self.config.redirect_port.is_none()
}
fn fixed_redirect_uri(&self) -> Option<String> {
if let Some(uri) = &self.config.redirect_uri {
return Some(uri.clone());
}
if let Some(port) = self.config.redirect_port {
return Some(format!("http://127.0.0.1:{port}/callback"));
}
None
}
fn extra_token_headers(&self) -> Vec<(&str, &str)> {
self.config
.extra_token_headers
.iter()
.map(|(k, v)| (k.as_str(), v.as_str()))
.collect()
}
fn extra_request_headers(&self) -> Vec<(&str, &str)> {
self.config
.extra_request_headers
.iter()
.map(|(k, v)| (k.as_str(), v.as_str()))
.collect()
}
fn flow(&self) -> OAuthFlow {
self.config.flow
}
fn echo_pkce_in_token_exchange(&self) -> bool {
self.config.echo_pkce_in_token_exchange
}
fn include_state_in_token_exchange(&self) -> bool {
self.config.include_state_in_token_exchange
}
}
+2 -2
View File
@@ -26,8 +26,8 @@ impl OAuthProvider for OpenAIOAuthProvider {
"http://localhost:1455/auth/callback"
}
fn scopes(&self) -> &str {
"openid profile email offline_access"
fn scopes(&self) -> String {
"openid profile email offline_access".to_string()
}
fn token_request_format(&self) -> TokenRequestFormat {