feat: add OAuthConfig + OAuthFlow types to oauth.rs

This commit is contained in:
2026-07-20 12:41:24 -06:00
parent d0a38747e0
commit 559107073d
+98
View File
@@ -5,6 +5,7 @@ use anyhow::{Result, anyhow, bail};
use base64::Engine; use base64::Engine;
use base64::engine::general_purpose::URL_SAFE_NO_PAD; use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use chrono::Utc; use chrono::Utc;
use indexmap::IndexMap;
use inquire::Text; use inquire::Text;
use reqwest::{Client as ReqwestClient, RequestBuilder}; use reqwest::{Client as ReqwestClient, RequestBuilder};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -17,11 +18,100 @@ use std::net::TcpListener;
use url::Url; use url::Url;
use uuid::Uuid; use uuid::Uuid;
#[derive(Debug, Clone, Copy, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TokenRequestFormat { pub enum TokenRequestFormat {
Json, Json,
FormUrlEncoded, FormUrlEncoded,
} }
#[derive(Debug, Clone, Copy, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum OAuthFlow {
#[default]
Pkce,
ClientCredentials,
}
/// Runtime OAuth configuration merged from `models.yaml` provider defaults
/// and user config `clients[i].oauth` overrides.
///
/// Every field except `client_id`, `token_url`, and `flow` is optional so that
/// user config can override individual fields without restating the entire block.
#[derive(Debug, Clone, Deserialize)]
pub struct OAuthConfig {
pub client_id: String,
pub token_url: String,
#[serde(default)]
pub flow: OAuthFlow,
pub client_secret: Option<String>,
pub authorize_url: Option<String>,
pub redirect_uri: Option<String>,
pub redirect_port: Option<u16>,
#[serde(default)]
pub scopes: Vec<String>,
pub token_request_format: Option<TokenRequestFormat>,
#[serde(default)]
pub extra_authorize_params: IndexMap<String, String>,
#[serde(default)]
pub extra_token_headers: IndexMap<String, String>,
#[serde(default)]
pub extra_request_headers: IndexMap<String, String>,
#[serde(default)]
pub echo_pkce_in_token_exchange: bool,
#[serde(default = "default_true")]
pub include_state_in_token_exchange: bool,
}
fn default_true() -> bool {
true
}
impl OAuthConfig {
/// Merge a user override into `self` field-by-field. User values win.
/// Uses `json_patch::merge`-like semantics (see `common.rs:apply_patch`) —
/// `None` in override means "keep base"; explicit values replace.
pub fn merge(mut self, override_cfg: OAuthConfig) -> Self {
self.client_id = override_cfg.client_id;
self.token_url = override_cfg.token_url;
self.flow = override_cfg.flow;
if override_cfg.client_secret.is_some() {
self.client_secret = override_cfg.client_secret;
}
if override_cfg.authorize_url.is_some() {
self.authorize_url = override_cfg.authorize_url;
}
if override_cfg.redirect_uri.is_some() {
self.redirect_uri = override_cfg.redirect_uri;
}
if override_cfg.redirect_port.is_some() {
self.redirect_port = override_cfg.redirect_port;
}
if !override_cfg.scopes.is_empty() {
self.scopes = override_cfg.scopes;
}
if override_cfg.token_request_format.is_some() {
self.token_request_format = override_cfg.token_request_format;
}
if !override_cfg.extra_authorize_params.is_empty() {
self.extra_authorize_params
.extend(override_cfg.extra_authorize_params);
}
if !override_cfg.extra_token_headers.is_empty() {
self.extra_token_headers
.extend(override_cfg.extra_token_headers);
}
if !override_cfg.extra_request_headers.is_empty() {
self.extra_request_headers
.extend(override_cfg.extra_request_headers);
}
self.echo_pkce_in_token_exchange = override_cfg.echo_pkce_in_token_exchange;
self.include_state_in_token_exchange = override_cfg.include_state_in_token_exchange;
self
}
}
pub trait OAuthProvider: Send + Sync { pub trait OAuthProvider: Send + Sync {
fn provider_name(&self) -> &str; fn provider_name(&self) -> &str;
fn client_id(&self) -> &str; fn client_id(&self) -> &str;
@@ -65,6 +155,14 @@ pub trait OAuthProvider: Send + Sync {
fn include_state_in_token_exchange(&self) -> bool { fn include_state_in_token_exchange(&self) -> bool {
true true
} }
fn flow(&self) -> OAuthFlow {
OAuthFlow::Pkce
}
fn echo_pkce_in_token_exchange(&self) -> bool {
false
}
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]