use crate::config::OidcProviderConfig; use anyhow::{anyhow, Result}; use openidconnect::core::{CoreClient, CoreProviderMetadata, CoreResponseType}; use openidconnect::reqwest::async_http_client; use openidconnect::{ AuthenticationFlow, AuthorizationCode, ClientId, ClientSecret, CsrfToken, IssuerUrl, Nonce, PkceCodeChallenge, PkceCodeVerifier, RedirectUrl, Scope, }; use std::collections::HashMap; #[derive(Clone)] pub struct OidcProvider { pub id: String, pub name: String, pub client: CoreClient, pub scopes: Vec, pub extra_params: HashMap, } pub async fn load_providers( providers: &[OidcProviderConfig], base_url: &str, ) -> Result> { let mut out = HashMap::new(); for provider in providers { let issuer = IssuerUrl::new(provider.issuer_url.clone())?; let metadata = CoreProviderMetadata::discover_async(issuer, async_http_client).await?; let redirect = format!( "{}/admin/api/auth/oidc/{}/callback", base_url.trim_end_matches('/'), provider.id ); let client = CoreClient::from_provider_metadata( metadata, ClientId::new(provider.client_id.clone()), Some(ClientSecret::new(provider.client_secret.clone())), ) .set_redirect_uri(RedirectUrl::new(redirect)?); out.insert( provider.id.clone(), OidcProvider { id: provider.id.clone(), name: provider.name.clone(), client, scopes: provider.scopes.clone(), extra_params: provider.extra_params.clone(), }, ); } Ok(out) } pub struct OidcAuthRequest { pub url: String, pub state: String, pub nonce: String, pub verifier: String, } pub fn build_auth_request(provider: &OidcProvider) -> Result { let (challenge, verifier) = PkceCodeChallenge::new_random_sha256(); let mut request = provider .client .authorize_url( AuthenticationFlow::::AuthorizationCode, CsrfToken::new_random, Nonce::new_random, ) .set_pkce_challenge(challenge); if provider.scopes.is_empty() { request = request.add_scope(Scope::new("openid".to_string())); request = request.add_scope(Scope::new("email".to_string())); request = request.add_scope(Scope::new("profile".to_string())); } else { for scope in &provider.scopes { request = request.add_scope(Scope::new(scope.clone())); } } for (key, value) in &provider.extra_params { request = request.add_extra_param(key, value); } let (url, csrf, nonce) = request.url(); Ok(OidcAuthRequest { url: url.to_string(), state: csrf.secret().to_string(), nonce: nonce.secret().to_string(), verifier: verifier.secret().to_string(), }) } pub async fn exchange_code( provider: &OidcProvider, code: String, verifier: String, ) -> Result { let token = provider .client .exchange_code(AuthorizationCode::new(code)) .set_pkce_verifier(PkceCodeVerifier::new(verifier)) .request_async(async_http_client) .await .map_err(|err| anyhow!("oidc token exchange failed: {}", err))?; Ok(token) }