diff options
-rw-r--r-- | indieauth/src/lib.rs | 115 | ||||
-rw-r--r-- | src/indieauth/mod.rs | 1 |
2 files changed, 115 insertions, 1 deletions
diff --git a/indieauth/src/lib.rs b/indieauth/src/lib.rs index cbe9085..96ddea4 100644 --- a/indieauth/src/lib.rs +++ b/indieauth/src/lib.rs @@ -212,9 +212,20 @@ pub struct Metadata { pub authorization_response_iss_parameter_supported: Option<bool>, /// The User Info Endpoint #[serde(skip_serializing_if = "Option::is_none")] - pub userinfo_endpoint: Option<Url> + pub userinfo_endpoint: Option<Url>, + /// Does the current authorization server support reading OAuth + /// Client ID metadata? + /// + /// This may help enable some non-IndieAuth implementations to + /// interoperate with IndieAuth by allowing dynamic client + /// registration. + #[serde(skip_serializing_if = "ref_identity")] + #[serde(default = "Default::default")] + pub client_id_metadata_document_supported: bool } +fn ref_identity(v: &bool) -> bool { *v } + #[cfg(feature = "axum")] impl axum_core::response::IntoResponse for Metadata { fn into_response(self) -> axum_core::response::Response { @@ -227,6 +238,108 @@ impl axum_core::response::IntoResponse for Metadata { } } +/// Client ID metadata, as specified in +/// https://datatracker.ietf.org/doc/html/draft-parecki-oauth-client-id-metadata-document. +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct ClientMetadata { + /// Client ID. This MUST match the URL this is served from. + // XXX: we really ought to do string comparison here. + pub client_id: Url, + /// URL of a web page providing more information about the client. + /// For IndieAuth, this MUST be a prefix of `client_id`. + pub client_uri: Url, + /// Human-friendly displayable name for the client. + #[serde(skip_serializing_if = "Option::is_none")] + pub client_name: Option<String>, + /// URL that refers to a logo for the client. + #[serde(skip_serializing_if = "Option::is_none")] + pub logo_uri: Option<Url>, + /// Registered redirect URIs for this application. + #[serde(skip_serializing_if = "Option::is_none")] + pub redirect_uris: Option<Vec<Url>>, + // The following is a random grab-bag of registry options that I + // deemed useful. Feel free to expand these as needed. + /// Grant types this application may use. + #[serde(skip_serializing_if = "Option::is_none")] + pub grant_types: Option<Vec<GrantType>>, + /// Response types this application may use. + #[serde(skip_serializing_if = "Option::is_none")] + pub response_types: Option<Vec<ResponseType>>, + /// Scopes that this client may request. + #[serde(skip_serializing_if = "Option::is_none")] + pub scope: Option<Scopes>, + /// Contact points for this client. + #[serde(skip_serializing_if = "Option::is_none")] + pub contacts: Option<Vec<String>>, + /// URI for the human-readable Terms of Service. + #[serde(skip_serializing_if = "Option::is_none")] + pub tos_uri: Option<Url>, + /// URI for the human-readable policy document. + #[serde(skip_serializing_if = "Option::is_none")] + pub policy_uri: Option<Url>, + /// Identifier for the client software. + #[serde(skip_serializing_if = "Option::is_none")] + pub software_id: Option<Cow<'static, str>>, + /// Version of the client software. + #[serde(skip_serializing_if = "Option::is_none")] + pub software_version: Option<Cow<'static, str>>, + /// URI for the homepage of this client's owners + #[serde(skip_serializing_if = "Option::is_none")] + pub homepage_uri: Option<Url> +} + +impl ClientMetadata { + /// Create a new [`ClientMetadata`] with all the optional fields + /// omitted. + /// + /// # Errors + /// + /// Returns `()` if the `client_uri` is not a prefix of + /// `client_id` as required by the IndieAuth spec. + pub fn new(client_id: url::Url, client_uri: url::Url) -> Result<Self, ()> { + if client_id.as_str().as_bytes()[..client_uri.as_str().len()] != *client_uri.as_str().as_bytes() { + return Err(()); + } + + Ok(Self { + client_id, client_uri, + client_name: None, + logo_uri: None, + redirect_uris: None, + grant_types: None, + response_types: None, + scope: None, + contacts: None, + tos_uri: None, + policy_uri: None, + software_id: None, + software_version: None, + homepage_uri: None, + }) + } + + /// Transform this into h-app metadata in mf2+html format for + /// compatibility with older identity providers that do not + /// recognize the new spec yet. + #[cfg(feature = "axum")] + pub fn into_html(self) -> axum_core::response::Response { + todo!() + } +} + +#[cfg(feature = "axum")] +impl axum_core::response::IntoResponse for ClientMetadata { + fn into_response(self) -> axum_core::response::Response { + use http::StatusCode; + + (StatusCode::OK, + [("Content-Type", "application/json")], + serde_json::to_vec(&self).unwrap()) + .into_response() + } +} + + /// User profile to be returned from the userinfo endpoint and when /// the `profile` scope was requested. #[derive(Clone, Debug, Serialize, Deserialize)] diff --git a/src/indieauth/mod.rs b/src/indieauth/mod.rs index c09b426..9f35ed0 100644 --- a/src/indieauth/mod.rs +++ b/src/indieauth/mod.rs @@ -135,6 +135,7 @@ pub async fn metadata( code_challenge_methods_supported: vec![PKCEMethod::S256], authorization_response_iss_parameter_supported: Some(true), userinfo_endpoint: Some(indieauth.join("userinfo").unwrap()), + client_id_metadata_document_supported: true, } } |