Skip to main content

zulip_api/message/fetch_api_key/
request.rs

1use serde::{Deserialize, Serialize};
2use zeroize::{Zeroize, ZeroizeOnDrop};
3
4use crate::{field_access, impl_display, Error, Result};
5
6/// Represents the email of the user to fetch an API key.
7#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
8pub struct Email {
9    email: String,
10}
11
12impl Email {
13    /// Creates a new [Email].
14    pub fn new<I: Into<String>>(email: I) -> Self {
15        Self {
16            email: email.into(),
17        }
18    }
19}
20
21field_access! {
22    Email {
23        /// Represents the user email used to fetch the API key.
24        email: as_ref { &str, String },
25    }
26}
27
28/// Represents the password of the user to fetch an API key.
29#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize, Zeroize, ZeroizeOnDrop)]
30pub struct Password {
31    password: String,
32}
33
34impl Password {
35    /// Creates a new [Password].
36    pub fn new<I: Into<String>>(password: I) -> Self {
37        Self {
38            password: password.into(),
39        }
40    }
41}
42
43field_access! {
44    Password {
45        /// Represents the user password used to fetch the API key.
46        password: as_ref { &str, String },
47    }
48}
49
50/// Represents a request to the `Fetch API Key` Zulip endpoint.
51///
52/// Allows third-party applications to use a username-password authentication method.
53///
54/// See <https://zulip.readthedocs.io/en/latest/production/authentication-methods.html#json-web-tokens-jwt>.
55#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize, Zeroize)]
56pub struct Request {
57    username: String,
58    password: String,
59    token: String,
60}
61
62impl Request {
63    /// Creates a signed JWT token for authenticating the `fetch_api_key` request.
64    ///
65    /// # Parameters
66    ///
67    /// - `algo`: JWT algorithm set in `/etc/zulip/settings.py` for the shared secret
68    /// - `key`: JWT shared secret set in `/etc/zulip/settings.py`
69    ///
70    /// The `key` argument should be constructed from the shared secret based on the algorithm.
71    ///
72    /// # Example
73    ///
74    /// ```rust,no_run
75    /// use zulip_api::fetch_api_key::{Email, Password, Request};
76    /// use zulip_api::jwt;
77    ///
78    /// // if HMAC-SHA256 (`HS256`) is set in `settings.py`
79    /// let algo = jwt::Algorithm::HS256;
80    /// let key = jwt::EncodingKey::from_base64_secret("MWFlMTgyZjg3NzM1MzMyYmUxNDEwYWMyM2RlMDRkNzRlOWIwNmI4Mjc2YjQwMmZmYWMzNjllNTA5ZThiN2I0Mg==").unwrap();
81    /// let email = Email::new("user@example.dev");
82    /// let password = Password::new("super_secret_password");
83    /// let _req = Request::create(algo, &key, &email, &password).unwrap();
84    /// ```
85    ///
86    /// See <https://zulip.readthedocs.io/en/latest/production/authentication-methods.html#json-web-tokens-jwt>.
87    pub fn create(
88        algo: jwt::Algorithm,
89        key: &jwt::EncodingKey,
90        email: &Email,
91        password: &Password,
92    ) -> Result<Self> {
93        jwt::encode(&jwt::Header::new(algo), email, key)
94            .map_err(|err| Error::io_other(format!("fetch_api_key: error signing request: {err}")))
95            .map(|token| Self {
96                username: email.email().into(),
97                password: password.password().into(),
98                token,
99            })
100    }
101}
102
103field_access! {
104    Request {
105        /// The username to fetch an API key using the `fetch_api_key` request.
106        ///
107        /// See <https://zulip.readthedocs.io/en/latest/production/authentication-methods.html#json-web-tokens-jwt>.
108        username: as_ref { &str, String },
109        /// The user password to fetch an API key using the `fetch_api_key` request.
110        ///
111        /// See <https://zulip.readthedocs.io/en/latest/production/authentication-methods.html#json-web-tokens-jwt>.
112        password: as_ref { &str, String },
113        /// The JWT token used to authenticate the `fetch_api_key` request.
114        ///
115        /// See <https://zulip.readthedocs.io/en/latest/production/authentication-methods.html#json-web-tokens-jwt>.
116        token: as_ref { &str, String },
117    }
118}
119
120impl_display!(Request);
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn test_request() {
128        let algo = jwt::Algorithm::HS256;
129        let key = jwt::EncodingKey::from_base64_secret("MWFlMTgyZjg3NzM1MzMyYmUxNDEwYWMyM2RlMDRkNzRlOWIwNmI4Mjc2YjQwMmZmYWMzNjllNTA5ZThiN2I0Mg==").unwrap();
130        let email = Email::new("user@example.dev");
131        let email_str = email.email();
132        let password = Password::new("super_secret_password");
133        let password_str = password.password();
134        let req = Request::create(algo, &key, &email, &password).unwrap();
135        let token = req.token();
136
137        let email_json = format!(r#"{{"email":"{email_str}"}}"#);
138        let req_json = format!(
139            r#"{{"username":"{email_str}","password":"{password_str}","token":"{token}"}}"#
140        );
141
142        assert_eq!(serde_json::to_string(&email).unwrap(), email_json);
143        assert_eq!(serde_json::from_str::<Email>(&email_json).unwrap(), email);
144
145        assert_eq!(serde_json::to_string(&req).unwrap(), req_json);
146        assert_eq!(serde_json::from_str::<Request>(&req_json).unwrap(), req);
147
148        assert_eq!(req.token(), token);
149    }
150}