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}