From 193d1cd7ba3b8b4226d76045bf5b66ed8daa9524 Mon Sep 17 00:00:00 2001 From: Vika Date: Sun, 18 Aug 2024 00:15:55 +0300 Subject: kittybox-indieauth: support OAuth2 Client Metadata Required for new revision of IndieAuth. --- indieauth/src/lib.rs | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 114 insertions(+), 1 deletion(-) (limited to 'indieauth/src') 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, /// The User Info Endpoint #[serde(skip_serializing_if = "Option::is_none")] - pub userinfo_endpoint: Option + pub userinfo_endpoint: Option, + /// 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, + /// URL that refers to a logo for the client. + #[serde(skip_serializing_if = "Option::is_none")] + pub logo_uri: Option, + /// Registered redirect URIs for this application. + #[serde(skip_serializing_if = "Option::is_none")] + pub redirect_uris: Option>, + // 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>, + /// Response types this application may use. + #[serde(skip_serializing_if = "Option::is_none")] + pub response_types: Option>, + /// Scopes that this client may request. + #[serde(skip_serializing_if = "Option::is_none")] + pub scope: Option, + /// Contact points for this client. + #[serde(skip_serializing_if = "Option::is_none")] + pub contacts: Option>, + /// URI for the human-readable Terms of Service. + #[serde(skip_serializing_if = "Option::is_none")] + pub tos_uri: Option, + /// URI for the human-readable policy document. + #[serde(skip_serializing_if = "Option::is_none")] + pub policy_uri: Option, + /// Identifier for the client software. + #[serde(skip_serializing_if = "Option::is_none")] + pub software_id: Option>, + /// Version of the client software. + #[serde(skip_serializing_if = "Option::is_none")] + pub software_version: Option>, + /// URI for the homepage of this client's owners + #[serde(skip_serializing_if = "Option::is_none")] + pub homepage_uri: Option +} + +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 { + 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)] -- cgit 1.4.1