about summary refs log tree commit diff
path: root/indieauth
diff options
context:
space:
mode:
authorVika <vika@fireburn.ru>2024-08-18 00:15:55 +0300
committerVika <vika@fireburn.ru>2024-08-18 00:27:53 +0300
commit193d1cd7ba3b8b4226d76045bf5b66ed8daa9524 (patch)
treea4b9eab7e378521eee25ab91c3519c28ab143ba0 /indieauth
parentf5587c32d42c8b0ce591affd2ef5d7a30c24257f (diff)
kittybox-indieauth: support OAuth2 Client Metadata
Required for new revision of IndieAuth.
Diffstat (limited to 'indieauth')
-rw-r--r--indieauth/src/lib.rs115
1 files changed, 114 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)]