From e681b081d8a08346b3e02b21eb07fd840335d199 Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Tue, 22 Nov 2022 22:14:41 +0100 Subject: [PATCH] Add authentication support --- Cargo.toml | 1 + README.md | 32 +++++++++---------- examples/hello_world_alert.rs | 10 ++++-- src/auth.rs | 23 ++++++++++++++ src/lib.rs | 58 ++++++++++++++--------------------- src/payload.rs | 38 +++++++++++++++++++++++ 6 files changed, 107 insertions(+), 55 deletions(-) create mode 100644 src/auth.rs create mode 100644 src/payload.rs diff --git a/Cargo.toml b/Cargo.toml index 3984a05..8977ebc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ default = [] blocking = ["reqwest/blocking"] [dependencies] +base64 = "0.13" log = "0.4" reqwest = { version = "0.11", features = ["json", "socks"] } serde = { version = "1.0", features = ["derive"] } diff --git a/README.md b/README.md index 7a55ef4..71639da 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,17 @@ tokio = { version = "1", features = ["full"] } ``` ```rust,no_run -use ntfy::{Dispatcher, Payload, Priority}; +use ntfy::{Dispatcher, Payload, Priority, Auth}; #[tokio::main] async fn main() { - let dispatcher = Dispatcher::new("https://ntfy.sh", Some("socks5h://127.0.0.1:9050")).unwrap(); + let auth = Auth::new("username", "password"); + let dispatcher = Dispatcher::new( + "https://ntfy.sh", + Some(auth), + Some("socks5h://127.0.0.1:9050"), + ) + .unwrap(); let payload = Payload::new("mytopic", "Hello, World!") .title("Alert") // Add optional title @@ -26,25 +32,15 @@ async fn main() { } ``` -## Blocking example +More examples can be found in the [examples](./examples/) directory. -```toml -ntfy = { version = "0.1", features = ["blocking"] } -``` - -```rust,no_run -use ntfy::{Dispatcher, Payload, Priority}; - -fn main() { - let dispatcher = Dispatcher::new("https://ntfy.sh", Some("socks5h://127.0.0.1:9050")).unwrap(); +## Crate Feature Flags - let payload = Payload::new("mytopic", "Hello, World!") - .title("Alert") // Add optional title - .priority(Priority::High); // Edit priority +The following crate feature flags are available: - dispatcher.send(&payload).unwrap(); -} -``` +| Feature | Default | Description | +| ------------------- | :-----: | -------------------------------------------------------------------------------------------------------------------------- | +| `blocking` | No | Needed if you want to use this library in not async/await context | ## License diff --git a/examples/hello_world_alert.rs b/examples/hello_world_alert.rs index 7c25926..a818872 100644 --- a/examples/hello_world_alert.rs +++ b/examples/hello_world_alert.rs @@ -1,11 +1,17 @@ // Copyright (c) 2022 Yuki Kishimoto // Distributed under the MIT software license -use ntfy::{Dispatcher, Payload, Priority}; +use ntfy::{Auth, Dispatcher, Payload, Priority}; #[tokio::main] async fn main() { - let dispatcher = Dispatcher::new("https://ntfy.sh", Some("socks5h://127.0.0.1:9050")).unwrap(); + let auth = Auth::new("username", "password"); + let dispatcher = Dispatcher::new( + "https://ntfy.sh", + Some(auth), + Some("socks5h://127.0.0.1:9050"), + ) + .unwrap(); /* let payload = Payload { topic: String::from("mytopic"), diff --git a/src/auth.rs b/src/auth.rs new file mode 100644 index 0000000..531c7cc --- /dev/null +++ b/src/auth.rs @@ -0,0 +1,23 @@ +// Copyright (c) 2022 Yuki Kishimoto +// Distributed under the MIT software license + +pub struct Auth { + username: String, + password: String, +} + +impl Auth { + pub fn new(username: S, password: S) -> Self + where + S: Into, + { + Self { + username: username.into(), + password: password.into(), + } + } + + pub fn as_base64(&self) -> String { + base64::encode(format!("{}:{}", self.username, self.password)) + } +} diff --git a/src/lib.rs b/src/lib.rs index 437c439..bed791e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ #[macro_use] extern crate serde; +use reqwest::header::{HeaderMap, HeaderValue, InvalidHeaderValue}; #[cfg(not(feature = "blocking"))] use reqwest::Client as ReqwestClient; #[cfg(not(feature = "blocking"))] @@ -18,8 +19,12 @@ use reqwest::blocking::RequestBuilder; use reqwest::{Proxy, StatusCode}; use thiserror::Error; +mod auth; +mod payload; mod priority; +pub use self::auth::Auth; +pub use self::payload::Payload; pub use self::priority::Priority; #[derive(Clone)] @@ -28,46 +33,14 @@ pub struct Dispatcher { client: ReqwestClient, } -#[derive(Debug, Serialize, Deserialize)] -pub struct Payload { - pub topic: String, - pub message: String, - #[serde(with = "priority")] - pub priority: Priority, - pub title: Option, -} - -impl Payload { - /// Create new payload - pub fn new(topic: &str, message: &str) -> Self { - Self { - topic: topic.into(), - message: message.into(), - priority: Priority::default(), - title: None, - } - } - - /// Set priority - pub fn priority(self, priority: Priority) -> Self { - Self { priority, ..self } - } - - /// Set title - pub fn title(self, title: &str) -> Self { - Self { - title: Some(title.into()), - ..self - } - } -} - #[derive(Debug, Error)] pub enum NtfyError { #[error("Failed to deserialize: {0}")] FailedToDeserialize(String), #[error("Reqwest error: {0}")] ReqwestError(reqwest::Error), + #[error("Invalid header value: {0}")] + InvalidHeaderValue(InvalidHeaderValue), #[error("Empty Response")] EmptyResponse, #[error("Bad Result")] @@ -102,9 +75,18 @@ pub enum NtfyError { impl Dispatcher { /// Create new dispatcher - pub fn new(url: &str, proxy: Option<&str>) -> Result { + pub fn new(url: &str, auth: Option, proxy: Option<&str>) -> Result { let mut client = ReqwestClient::builder(); + if let Some(auth) = auth { + let mut headers = HeaderMap::new(); + let mut auth_value = HeaderValue::from_str(&format!("Basic {}", auth.as_base64()))?; + auth_value.set_sensitive(true); + headers.insert("Authorization", auth_value); + + client = client.default_headers(headers); + } + if let Some(proxy) = proxy { client = client.proxy(Proxy::all(proxy)?); } @@ -199,3 +181,9 @@ impl From for NtfyError { NtfyError::ReqwestError(err) } } + +impl From for NtfyError { + fn from(err: InvalidHeaderValue) -> Self { + NtfyError::InvalidHeaderValue(err) + } +} diff --git a/src/payload.rs b/src/payload.rs new file mode 100644 index 0000000..f7f9e8a --- /dev/null +++ b/src/payload.rs @@ -0,0 +1,38 @@ +// Copyright (c) 2022 Yuki Kishimoto +// Distributed under the MIT software license + +use crate::priority::{self, Priority}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Payload { + pub topic: String, + pub message: String, + #[serde(with = "priority")] + pub priority: Priority, + pub title: Option, +} + +impl Payload { + /// Create new payload + pub fn new(topic: &str, message: &str) -> Self { + Self { + topic: topic.into(), + message: message.into(), + priority: Priority::default(), + title: None, + } + } + + /// Set priority + pub fn priority(self, priority: Priority) -> Self { + Self { priority, ..self } + } + + /// Set title + pub fn title(self, title: &str) -> Self { + Self { + title: Some(title.into()), + ..self + } + } +}