zulip-api 0.0.8

Client library for interacting with the Zulip API
Documentation
use serde::{Deserialize, Serialize};
use zeroize::{Zeroize, ZeroizeOnDrop};

use crate::{field_access, impl_display, Error, Result};

/// Represents the email of the user to fetch an API key.
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct Email {
    email: String,
}

impl Email {
    /// Creates a new [Email].
    pub fn new<I: Into<String>>(email: I) -> Self {
        Self {
            email: email.into(),
        }
    }
}

field_access! {
    Email {
        /// Represents the user email used to fetch the API key.
        email: as_ref { &str, String },
    }
}

/// Represents the password of the user to fetch an API key.
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize, Zeroize, ZeroizeOnDrop)]
pub struct Password {
    password: String,
}

impl Password {
    /// Creates a new [Password].
    pub fn new<I: Into<String>>(password: I) -> Self {
        Self {
            password: password.into(),
        }
    }
}

field_access! {
    Password {
        /// Represents the user password used to fetch the API key.
        password: as_ref { &str, String },
    }
}

/// Represents a request to the `Fetch API Key` Zulip endpoint.
///
/// Allows third-party applications to use a username-password authentication method.
///
/// See <https://zulip.readthedocs.io/en/latest/production/authentication-methods.html#json-web-tokens-jwt>.
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize, Zeroize)]
pub struct Request {
    username: String,
    password: String,
    token: String,
}

impl Request {
    /// Creates a signed JWT token for authenticating the `fetch_api_key` request.
    ///
    /// # Parameters
    ///
    /// - `algo`: JWT algorithm set in `/etc/zulip/settings.py` for the shared secret
    /// - `key`: JWT shared secret set in `/etc/zulip/settings.py`
    ///
    /// The `key` argument should be constructed from the shared secret based on the algorithm.
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// use zulip_api::fetch_api_key::{Email, Password, Request};
    /// use zulip_api::jwt;
    ///
    /// // if HMAC-SHA256 (`HS256`) is set in `settings.py`
    /// let algo = jwt::Algorithm::HS256;
    /// let key = jwt::EncodingKey::from_base64_secret("MWFlMTgyZjg3NzM1MzMyYmUxNDEwYWMyM2RlMDRkNzRlOWIwNmI4Mjc2YjQwMmZmYWMzNjllNTA5ZThiN2I0Mg==").unwrap();
    /// let email = Email::new("user@example.dev");
    /// let password = Password::new("super_secret_password");
    /// let _req = Request::create(algo, &key, &email, &password).unwrap();
    /// ```
    ///
    /// See <https://zulip.readthedocs.io/en/latest/production/authentication-methods.html#json-web-tokens-jwt>.
    pub fn create(
        algo: jwt::Algorithm,
        key: &jwt::EncodingKey,
        email: &Email,
        password: &Password,
    ) -> Result<Self> {
        jwt::encode(&jwt::Header::new(algo), email, key)
            .map_err(|err| Error::io_other(format!("fetch_api_key: error signing request: {err}")))
            .map(|token| Self {
                username: email.email().into(),
                password: password.password().into(),
                token,
            })
    }
}

field_access! {
    Request {
        /// The username to fetch an API key using the `fetch_api_key` request.
        ///
        /// See <https://zulip.readthedocs.io/en/latest/production/authentication-methods.html#json-web-tokens-jwt>.
        username: as_ref { &str, String },
        /// The user password to fetch an API key using the `fetch_api_key` request.
        ///
        /// See <https://zulip.readthedocs.io/en/latest/production/authentication-methods.html#json-web-tokens-jwt>.
        password: as_ref { &str, String },
        /// The JWT token used to authenticate the `fetch_api_key` request.
        ///
        /// See <https://zulip.readthedocs.io/en/latest/production/authentication-methods.html#json-web-tokens-jwt>.
        token: as_ref { &str, String },
    }
}

impl_display!(Request);

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_request() {
        let algo = jwt::Algorithm::HS256;
        let key = jwt::EncodingKey::from_base64_secret("MWFlMTgyZjg3NzM1MzMyYmUxNDEwYWMyM2RlMDRkNzRlOWIwNmI4Mjc2YjQwMmZmYWMzNjllNTA5ZThiN2I0Mg==").unwrap();
        let email = Email::new("user@example.dev");
        let email_str = email.email();
        let password = Password::new("super_secret_password");
        let password_str = password.password();
        let req = Request::create(algo, &key, &email, &password).unwrap();
        let token = req.token();

        let email_json = format!(r#"{{"email":"{email_str}"}}"#);
        let req_json = format!(
            r#"{{"username":"{email_str}","password":"{password_str}","token":"{token}"}}"#
        );

        assert_eq!(serde_json::to_string(&email).unwrap(), email_json);
        assert_eq!(serde_json::from_str::<Email>(&email_json).unwrap(), email);

        assert_eq!(serde_json::to_string(&req).unwrap(), req_json);
        assert_eq!(serde_json::from_str::<Request>(&req_json).unwrap(), req);

        assert_eq!(req.token(), token);
    }
}