From a97356802067929701d64dd58bfeff4b84b117f0 Mon Sep 17 00:00:00 2001 From: w3irdrobot Date: Wed, 26 Jul 2023 17:32:54 -0400 Subject: [PATCH] add httpdata struct to get http auth information from tags in event --- crates/nostr/README.md | 2 +- crates/nostr/src/lib.rs | 2 +- crates/nostr/src/nips/mod.rs | 1 + crates/nostr/src/nips/nip98.rs | 142 +++++++++++++++++++++++++++++++++ 4 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 crates/nostr/src/nips/nip98.rs diff --git a/crates/nostr/README.md b/crates/nostr/README.md index 0946bdaf9..aba5eb836 100644 --- a/crates/nostr/README.md +++ b/crates/nostr/README.md @@ -167,4 +167,4 @@ This project is distributed under the MIT software license - see the [LICENSE](. ⚡ Tips: -⚡ Lightning Address: yuki@getalby.com \ No newline at end of file +⚡ Lightning Address: yuki@getalby.com diff --git a/crates/nostr/src/lib.rs b/crates/nostr/src/lib.rs index da1429b56..01200faea 100644 --- a/crates/nostr/src/lib.rs +++ b/crates/nostr/src/lib.rs @@ -30,7 +30,7 @@ pub mod prelude; pub mod types; pub use self::event::tag::{ - ExternalIdentity, Identity, ImageDimensions, Marker, Report, Tag, TagKind, + ExternalIdentity, HttpMethod, Identity, ImageDimensions, Marker, Report, Tag, TagKind, }; pub use self::event::{Event, EventBuilder, EventId, Kind, UnsignedEvent}; pub use self::key::Keys; diff --git a/crates/nostr/src/nips/mod.rs b/crates/nostr/src/nips/mod.rs index a8a8aa7eb..380277dc5 100644 --- a/crates/nostr/src/nips/mod.rs +++ b/crates/nostr/src/nips/mod.rs @@ -27,3 +27,4 @@ pub mod nip53; pub mod nip58; pub mod nip65; pub mod nip94; +pub mod nip98; diff --git a/crates/nostr/src/nips/nip98.rs b/crates/nostr/src/nips/nip98.rs new file mode 100644 index 000000000..9b6a0f3f8 --- /dev/null +++ b/crates/nostr/src/nips/nip98.rs @@ -0,0 +1,142 @@ +// Copyright (c) 2022-2023 Yuki Kishimoto +// Distributed under the MIT software license + +//! NIP98 +//! +//! This NIP defines an ephemerial event used to authorize requests to HTTP servers using nostr events. +//! This is useful for HTTP services which are build for Nostr and deal with Nostr user accounts. +//! +use core::fmt; + +use crate::{HttpMethod, Tag, UncheckedUrl}; +use bitcoin_hashes::sha256::Hash as Sha256Hash; + +/// [`HttpData`] required tags +#[derive(Debug)] +pub enum RequiredTags { + /// [`Tag::AbsoluteURL`] + AbsoluteURL, + /// [`Tag::Method`] + Method, +} + +impl fmt::Display for RequiredTags { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::AbsoluteURL => write!(f, "url"), + Self::Method => write!(f, "method"), + } + } +} + +/// [`HttpData`] error +#[derive(Debug)] +pub enum Error { + /// Hex decoding error + Hex(bitcoin_hashes::hex::Error), + /// Tag missing when parsing + MissingTag(RequiredTags), +} + +impl std::error::Error for Error {} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Hex(e) => write!(f, "{e}"), + Self::MissingTag(tag) => write!(f, r#"missing tag "{tag}""#), + } + } +} + +impl From for Error { + fn from(e: bitcoin_hashes::hex::Error) -> Self { + Self::Hex(e) + } +} + +/// HTTP Data +pub struct HttpData { + /// absolute request URL + pub url: UncheckedUrl, + /// HTTP method + pub method: HttpMethod, + /// SHA256 hash of the request body + pub payload: Option, +} + +impl HttpData { + /// New [`HttpData`] + pub fn new(url: UncheckedUrl, method: HttpMethod) -> Self { + Self { + url, + method, + payload: None, + } + } + + /// Add hex-encoded SHA256 hash of the request body + pub fn payload(self, payload: Sha256Hash) -> Self { + Self { + payload: Some(payload), + ..self + } + } +} + +impl From for Vec { + fn from(value: HttpData) -> Self { + let mut tags = Vec::new(); + + let HttpData { + url, + method, + payload, + } = value; + + tags.push(Tag::AbsoluteURL(url)); + tags.push(Tag::Method(method)); + + if let Some(payload) = payload { + tags.push(Tag::Payload(payload)); + } + + tags + } +} + +impl TryFrom> for HttpData { + type Error = Error; + + fn try_from(value: Vec) -> Result { + let url = value + .iter() + .find_map(|t| match t { + Tag::AbsoluteURL(u) => Some(u), + _ => None, + }) + .cloned() + .ok_or(Error::MissingTag(RequiredTags::AbsoluteURL))?; + let method = value + .iter() + .find_map(|t| match t { + Tag::Method(m) => Some(m), + _ => None, + }) + .cloned() + .ok_or(Error::MissingTag(RequiredTags::Method))?; + let payload = value + .iter() + .find_map(|t| match t { + Tag::Payload(p) => Some(p), + _ => None, + }) + .cloned(); + + Ok(Self { + url, + method, + payload, + }) + } +}