use crate::rest::openid::shared::{
get_authorization_providers, AuthorizationProvider,
};
use actix_web::{get, web, HttpResponse, Responder};
use awc::Client;
use myc_config::optional_config::OptionalConfig;
use myc_core::models::AccountLifeCycle;
use myc_http_tools::{
models::auth_config::AuthConfig, settings::DEFAULT_CONNECTION_STRING_KEY,
};
use serde::{Deserialize, Serialize};
use utoipa::{ToResponse, ToSchema};
pub fn configure(config: &mut web::ServiceConfig) {
config
.service(well_known_oauth_authorization_server)
.service(well_known_protected_resource);
}
#[derive(Debug, Clone, Deserialize, Serialize, ToResponse, ToSchema)]
#[serde(rename_all = "snake_case")]
struct ProtectedResourceAuthServer {
issuer: String,
audience: String,
}
#[derive(Debug, Clone, Deserialize, Serialize, ToResponse, ToSchema)]
#[serde(rename_all = "snake_case")]
struct ProtectedResource {
resource: String,
authorization_servers: Vec<ProtectedResourceAuthServer>,
scopes_supported: Vec<String>,
bearer_methods_supported: Vec<String>,
resource_documentation: String,
}
#[utoipa::path(
get,
operation_id = "get_well_known_oauth_authorization_server",
responses(
(
status = 200,
description = "Well known oauth authorization server.",
body = AuthorizationProvider,
),
),
)]
#[get("/.well-known/oauth-authorization-server")]
pub async fn well_known_oauth_authorization_server(
auth_config: web::Data<AuthConfig>,
client: web::Data<Client>,
) -> impl Responder {
let auth_config = auth_config.get_ref();
let external_config =
if let OptionalConfig::Enabled(config) = &auth_config.external {
config
} else {
return HttpResponse::NotFound()
.body("External providers are not configured");
};
let eligible_providers = external_config
.iter()
.filter(|provider| provider.discovery_url.is_some())
.map(|provider| provider.clone())
.collect::<Vec<_>>();
let authorization_providers = match get_authorization_providers(
auth_config,
Some(eligible_providers),
)
.await
{
Ok(providers) => providers,
Err(error) => {
return error;
}
};
if authorization_providers.is_empty() {
return HttpResponse::NotFound()
.body("No authorization providers are configured");
}
let provider = authorization_providers.iter().next().unwrap();
match client.get(provider.discovery_url.clone()).send().await {
Err(err) => {
tracing::error!(
"Error fetching well known oauth authorization server from provider {}: {err}",
provider.issuer
);
HttpResponse::InternalServerError().finish()
}
Ok(mut res) => {
let body = res.body().await.unwrap_or_else(|_| web::Bytes::new());
HttpResponse::build(res.status())
.content_type("application/json")
.body(body)
}
}
}
#[utoipa::path(
get,
operation_id = "get_well_known_oauth_protected_resource",
responses(
(
status = 200,
description = "Well known oauth protected resource.",
body = AuthorizationProvider,
),
),
)]
#[get("/.well-known/oauth-protected-resource")]
pub async fn well_known_protected_resource(
auth_config: web::Data<AuthConfig>,
account_life_cycle: web::Data<AccountLifeCycle>,
) -> impl Responder {
let auth_config = auth_config.get_ref();
let authorization_servers =
match get_authorization_providers(auth_config, None).await {
Ok(providers) => providers
.iter()
.map(|p| ProtectedResourceAuthServer {
issuer: p.issuer.clone(),
audience: p.audience.clone(),
})
.collect::<Vec<_>>(),
Err(error) => {
return error;
}
};
let resource = if let Some(domain_url) =
account_life_cycle.domain_url.clone()
{
domain_url.async_get_or_error().await.unwrap()
} else {
return HttpResponse::NotFound().body("Domain URL is not configured");
};
let protected_resource = ProtectedResource {
resource,
authorization_servers,
scopes_supported: vec![],
bearer_methods_supported: vec![
"header".to_string(),
DEFAULT_CONNECTION_STRING_KEY.to_string(),
],
resource_documentation:
"https://lepistabioinformatics.github.io/mycelium-docs/".to_string(),
};
HttpResponse::Ok().json(protected_resource)
}