From 8a88bb3e3d0657f78424cf2c48905314bf066dce Mon Sep 17 00:00:00 2001 From: Julia Lange Date: Thu, 28 Aug 2025 10:53:36 -0700 Subject: [PATCH] Router, implement OAuth authorization server metadata endpoint --- .../wellknown/oauth/authorization_server.rs | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/router/src/wellknown/oauth/authorization_server.rs b/router/src/wellknown/oauth/authorization_server.rs index 139597f..d2ae12f 100644 --- a/router/src/wellknown/oauth/authorization_server.rs +++ b/router/src/wellknown/oauth/authorization_server.rs @@ -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; +} +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>, + additional_grant_types_supported: Option>, + additional_code_challenge_methods_supported: Option>, + additional_token_endpoint_auth_methods_supported: Option>, + additional_token_endpoint_auth_signing_alg_values_supported: Option>, + additional_scopes_supported: Option>, + pushed_authorization_request_endpoint: String, + additional_dpop_signing_alg_values_supported: Option>, +} + +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 { + 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, + })) + } +}