reduct_rs/client/
token.rs1use super::{ReductClient, Result};
2use crate::http_client::HttpClient;
3use chrono::{DateTime, Utc};
4use reduct_base::msg::token_api::{
5 Permissions, Token, TokenCreateRequest, TokenCreateResponse, TokenList,
6};
7use reqwest::Method;
8use std::sync::Arc;
9use std::time::Duration;
10
11pub struct CreateTokenBuilder {
13 name: String,
14 request: TokenCreateRequest,
15 http_client: Arc<HttpClient>,
16}
17
18impl CreateTokenBuilder {
19 pub(super) fn new(name: String, http_client: Arc<HttpClient>) -> Self {
20 Self {
21 name,
22 request: TokenCreateRequest::default(),
23 http_client,
24 }
25 }
26
27 pub fn permissions(mut self, permissions: Permissions) -> Self {
29 self.request.permissions = permissions;
30 self
31 }
32
33 pub fn expires_at(mut self, expires_at: DateTime<Utc>) -> Self {
35 self.request.expires_at = Some(expires_at);
36 self
37 }
38
39 pub fn ttl(mut self, ttl: Duration) -> Self {
41 self.request.ttl = Some(ttl.as_secs());
42 self
43 }
44
45 pub fn ip_allowlist(mut self, ip_allowlist: Vec<String>) -> Self {
47 self.request.ip_allowlist = ip_allowlist;
48 self
49 }
50
51 pub async fn send(self) -> Result<TokenCreateResponse> {
53 self.http_client
54 .send_and_receive_json::<TokenCreateRequest, TokenCreateResponse>(
55 Method::POST,
56 &format!("/tokens/{}", self.name),
57 Some(self.request),
58 )
59 .await
60 }
61}
62
63impl ReductClient {
64 pub async fn me(&self) -> Result<Token> {
70 self.http_client
71 .send_and_receive_json::<(), Token>(Method::GET, "/me", None)
72 .await
73 }
74
75 pub async fn me_info(&self) -> Result<Token> {
77 self.http_client
78 .send_and_receive_json::<(), Token>(Method::GET, "/me", None)
79 .await
80 }
81
82 pub async fn create_token(&self, name: &str, permissions: Permissions) -> Result<String> {
84 let token = self
85 .create_token_builder(name)
86 .permissions(permissions)
87 .send()
88 .await?;
89 Ok(token.value)
90 }
91
92 pub fn create_token_builder(&self, name: &str) -> CreateTokenBuilder {
95 CreateTokenBuilder::new(name.to_string(), Arc::clone(&self.http_client))
96 }
97
98 pub async fn rotate_token(&self, name: &str) -> Result<TokenCreateResponse> {
100 self.http_client
101 .send_and_receive_json::<(), TokenCreateResponse>(
102 Method::POST,
103 &format!("/tokens/{}/rotate", name),
104 None,
105 )
106 .await
107 }
108
109 pub async fn get_token(&self, name: &str) -> Result<Token> {
119 self.http_client
120 .send_and_receive_json::<(), Token>(Method::GET, &format!("/tokens/{}", name), None)
121 .await
122 }
123
124 pub async fn delete_token(&self, name: &str) -> Result<()> {
134 let request = self
135 .http_client
136 .request(Method::DELETE, &format!("/tokens/{}", name));
137 self.http_client.send_request(request).await?;
138 Ok(())
139 }
140
141 pub async fn list_tokens(&self) -> Result<Vec<Token>> {
147 let list = self
148 .http_client
149 .send_and_receive_json::<(), TokenList>(Method::GET, "/tokens", None)
150 .await?;
151 Ok(list.tokens)
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158 use crate::client::tests::client;
159 use rstest::rstest;
160
161 #[rstest]
162 #[tokio::test]
163 async fn test_me(#[future] client: ReductClient) {
164 let token = client.await.me().await.unwrap();
165 assert_eq!(token.name, "init-token");
166 assert!(token.permissions.unwrap().full_access);
167 }
168
169 #[rstest]
170 #[tokio::test]
171 async fn test_me_info(#[future] client: ReductClient) {
172 let token = client.await.me_info().await.unwrap();
173 assert_eq!(token.name, "init-token");
174 assert!(token.permissions.unwrap().full_access);
175 }
176
177 #[rstest]
178 #[tokio::test]
179 async fn test_create_token(#[future] client: ReductClient) {
180 let token_value = client
181 .await
182 .create_token(
183 "test-token",
184 Permissions {
185 full_access: false,
186 read: vec!["test-bucket".to_string()],
187 write: vec!["test-bucket".to_string()],
188 },
189 )
190 .await
191 .unwrap();
192
193 assert!(token_value.starts_with("test-token"));
194 }
195
196 #[cfg(feature = "test-api-119")]
197 #[rstest]
198 #[tokio::test]
199 async fn test_create_token_builder(#[future] client: ReductClient) {
200 let token = client
201 .await
202 .create_token_builder("test-token-options")
203 .permissions(Permissions {
204 full_access: false,
205 read: vec!["test-bucket".to_string()],
206 write: vec!["test-bucket".to_string()],
207 })
208 .ttl(Duration::from_secs(3600))
209 .ip_allowlist(vec!["127.0.0.1".to_string()])
210 .send()
211 .await
212 .unwrap();
213
214 assert!(token.value.starts_with("test-token-options"));
215 }
216
217 #[rstest]
218 #[tokio::test]
219 async fn test_get_token(#[future] client: ReductClient) {
220 let token = client.await.get_token("init-token").await.unwrap();
221 assert_eq!(token.name, "init-token");
222 assert!(token.is_provisioned);
223
224 let permissions = token.permissions.unwrap();
225 assert!(permissions.full_access);
226 assert!(permissions.read.is_empty());
227 assert!(permissions.write.is_empty());
228 }
229
230 #[rstest]
231 #[tokio::test]
232 async fn test_list_tokens(#[future] client: ReductClient) {
233 let tokens = client.await.list_tokens().await.unwrap();
234 assert!(!tokens.is_empty());
235 }
236
237 #[cfg(feature = "test-api-119")]
238 #[rstest]
239 #[tokio::test]
240 async fn test_rotate_token(#[future] client: ReductClient) {
241 let client = client.await;
242 client
243 .create_token("test-token-rotate", Permissions::default())
244 .await
245 .unwrap();
246 let rotated = client.rotate_token("test-token-rotate").await.unwrap();
247 assert!(rotated.value.starts_with("test-token-rotate"));
248 client.delete_token("test-token-rotate").await.unwrap();
249 }
250
251 #[rstest]
252 #[tokio::test]
253 async fn delete_token(#[future] client: ReductClient) {
254 let client = client.await;
255 client
256 .create_token("test-token", Permissions::default())
257 .await
258 .unwrap();
259 client.delete_token("test-token").await.unwrap();
260 }
261}