use std::sync::Arc;
use serde::Deserialize;
use crate::application::oauth::registration::{ClientMetadata, DynamicRegistrar, RegistrarError};
use crate::domain::oauth::registration::{ClientId, ClientSecret, RegisteredClient};
const MAX_RESPONSE_BYTES: usize = 64 * 1024;
#[derive(Debug, Clone)]
pub struct Rfc7591Registrar {
endpoint: Arc<str>,
http: reqwest::Client,
}
impl Rfc7591Registrar {
#[must_use]
pub fn new(endpoint: impl Into<Arc<str>>) -> Self {
Self {
endpoint: endpoint.into(),
http: reqwest::Client::new(),
}
}
#[must_use]
pub fn with_http_client(mut self, http: reqwest::Client) -> Self {
self.http = http;
self
}
#[must_use]
pub fn endpoint(&self) -> &str {
&self.endpoint
}
}
impl DynamicRegistrar for Rfc7591Registrar {
async fn register_dynamic(
&self,
issuer: &str,
metadata: &ClientMetadata,
) -> Result<RegisteredClient, RegistrarError> {
let body = serde_json::to_vec(metadata)
.map_err(|failure| RegistrarError::Malformed(failure.to_string()))?;
let response = self
.http
.post(&*self.endpoint)
.header(reqwest::header::CONTENT_TYPE, "application/json")
.body(body)
.send()
.await
.map_err(|failure| RegistrarError::Unavailable(failure.to_string()))?;
let status = response.status();
if !status.is_success() {
return Err(RegistrarError::Refused {
status: status.as_u16(),
});
}
let body = read_capped(response).await?;
credential(issuer, &body)
}
}
fn credential(issuer: &str, body: &[u8]) -> Result<RegisteredClient, RegistrarError> {
let parsed: RegistrationResponse = serde_json::from_slice(body)
.map_err(|failure| RegistrarError::Malformed(failure.to_string()))?;
Ok(RegisteredClient::dynamic(
issuer,
ClientId::opaque(&parsed.client_id),
parsed.client_secret.map(ClientSecret::issued),
)?)
}
#[derive(Debug, Deserialize)]
struct RegistrationResponse {
client_id: String,
#[serde(default)]
client_secret: Option<String>,
}
async fn read_capped(mut response: reqwest::Response) -> Result<Vec<u8>, RegistrarError> {
let mut body: Vec<u8> = Vec::new();
while let Some(chunk) = response
.chunk()
.await
.map_err(|failure| RegistrarError::Unavailable(failure.to_string()))?
{
if body.len().saturating_add(chunk.len()) > MAX_RESPONSE_BYTES {
return Err(RegistrarError::Malformed(format!(
"the registration response exceeded {MAX_RESPONSE_BYTES} bytes",
)));
}
body.extend_from_slice(&chunk);
}
Ok(body)
}