Router, implement OAuth authorization server metadata endpoint

This commit is contained in:
Julia Lange 2025-08-28 10:53:36 -07:00
parent 3ea0861d8f
commit 8a88bb3e3d
Signed by: Julia
SSH key fingerprint: SHA256:5DJcfxa5/fKCYn57dcabJa2vN2e6eT0pBerYi5SUbto

View file

@ -1,2 +1,103 @@
use serde::de::{
Error,
Unexpected,
};
use serde_json::{
json,
Result,
Value
};
use bon::Builder;
trait Metadata {
fn format_metadata(self, required: RequiredMetadata) -> Result<Value>;
}
pub struct RequiredMetadata {
issuer: String,
authorization_endpoint: String,
token_endpoint: String,
}
impl RequiredMetadata {
fn new(
issuer: String,
authorization_endpoint: String,
token_endpoint: String
) -> Self {
RequiredMetadata {
issuer, authorization_endpoint, token_endpoint
}
}
}
#[derive(Builder)]
struct AtprotoMetadata {
additional_response_types_supported: Option<Vec<String>>,
additional_grant_types_supported: Option<Vec<String>>,
additional_code_challenge_methods_supported: Option<Vec<String>>,
additional_token_endpoint_auth_methods_supported: Option<Vec<String>>,
additional_token_endpoint_auth_signing_alg_values_supported: Option<Vec<String>>,
additional_scopes_supported: Option<Vec<String>>,
pushed_authorization_request_endpoint: String,
additional_dpop_signing_alg_values_supported: Option<Vec<String>>,
}
impl AtprotoMetadata {
fn check_fields(&self) -> Result<()> {
// TODO: Issuer check (https scheme, no default port, no path segments
if self.additional_token_endpoint_auth_signing_alg_values_supported
.as_ref()
.is_none_or(|vec| vec.iter().any(|s| s == "none")) {
return Err(Error::invalid_value(
Unexpected::Other("\"none\" in token_endpoint_auth_signing_alg_values_supported"),
&"\"none\" to be omitted from token_endpoint_auth_signing_alg_values_supported"
));
}
Ok(())
}
}
impl Metadata for AtprotoMetadata {
fn format_metadata(self, required: RequiredMetadata) -> Result<Value> {
self.check_fields()?;
Ok(json!({
"issuer": required.issuer,
"authorization_endpoint": required.authorization_endpoint,
"token_endpoint": required.token_endpoint,
"response_types_supported":
self.additional_response_types_supported.unwrap_or_default()
.extend(["code".to_string()]),
"grant_types_supported":
self.additional_grant_types_supported.unwrap_or_default()
.extend([
"authorization_code".to_string(),
"refresh_token".to_string()
]),
"code_challenge_methods_supported":
self.additional_code_challenge_methods_supported.unwrap_or_default()
.extend(["S256".to_string()]),
"token_endpoint_auth_methods_supported":
self.additional_token_endpoint_auth_methods_supported.unwrap_or_default()
.extend([
"none".to_string(),
"private_key_jwt".to_string()
]),
"token_endpoint_auth_signing_alg_values_supported":
self.additional_token_endpoint_auth_signing_alg_values_supported.unwrap_or_default()
.extend(["ES256".to_string()]),
"scopes_supported":
self.additional_scopes_supported.unwrap_or_default()
.extend(["atproto".to_string()]),
"authorization_response_iss_parameter_supported": true,
"require_pushed_authorization_requests": true,
"pushed_authorization_request_endpoint": self.pushed_authorization_request_endpoint,
"dpop_signing_alg_values_supported":
self.additional_dpop_signing_alg_values_supported.unwrap_or_default()
.extend(["ES256".to_string()]),
"client_id_metadata_document_supported": true,
}))
}
}