109 lines
3.4 KiB
Rust
109 lines
3.4 KiB
Rust
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<String>,
|
|
pub extra_params: HashMap<String, String>,
|
|
}
|
|
|
|
pub async fn load_providers(
|
|
providers: &[OidcProviderConfig],
|
|
base_url: &str,
|
|
) -> Result<HashMap<String, OidcProvider>> {
|
|
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<OidcAuthRequest> {
|
|
let (challenge, verifier) = PkceCodeChallenge::new_random_sha256();
|
|
let mut request = provider
|
|
.client
|
|
.authorize_url(
|
|
AuthenticationFlow::<CoreResponseType>::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<openidconnect::core::CoreTokenResponse> {
|
|
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)
|
|
}
|