1use super::VectorizerClient;
13use crate::error::{Result, VectorizerError};
14use crate::models::{
15 ApiKey, ApiKeyUsageReport, ApiKeyView, AuditEntry, AuditQuery, CreateApiKeyRequest,
16 CreateScopedApiKeyRequest, CreateUserRequest, JwtToken, PasswordPolicyReport, RotatedKey,
17 TokenIntrospection, UpdateApiKeyPermissionsRequest, User,
18};
19
20impl VectorizerClient {
21 pub async fn me(&self) -> Result<User> {
26 let response = self.make_request("GET", "/auth/me", None).await?;
27 serde_json::from_str(&response)
28 .map_err(|e| VectorizerError::server(format!("Failed to parse me response: {e}")))
29 }
30
31 pub async fn logout(&self) -> Result<()> {
36 self.make_request("POST", "/auth/logout", None).await?;
37 Ok(())
38 }
39
40 pub async fn refresh_token(&self) -> Result<JwtToken> {
44 let response = self
45 .make_request("POST", "/auth/refresh", Some(serde_json::json!({})))
46 .await?;
47 serde_json::from_str(&response).map_err(|e| {
48 VectorizerError::server(format!("Failed to parse refresh_token response: {e}"))
49 })
50 }
51
52 pub async fn validate_password(&self, password: &str) -> Result<PasswordPolicyReport> {
57 let payload = serde_json::json!({ "password": password });
58 let response = self
59 .make_request("POST", "/auth/validate-password", Some(payload))
60 .await?;
61 serde_json::from_str(&response).map_err(|e| {
62 VectorizerError::server(format!("Failed to parse validate_password response: {e}"))
63 })
64 }
65
66 pub async fn create_api_key(&self, request: CreateApiKeyRequest) -> Result<ApiKey> {
71 let payload = serde_json::to_value(&request).map_err(|e| {
72 VectorizerError::server(format!("Failed to serialize create_api_key request: {e}"))
73 })?;
74 let response = self
75 .make_request("POST", "/auth/keys", Some(payload))
76 .await?;
77 serde_json::from_str(&response).map_err(|e| {
78 VectorizerError::server(format!("Failed to parse create_api_key response: {e}"))
79 })
80 }
81
82 pub async fn list_api_keys(&self) -> Result<Vec<ApiKey>> {
87 let response = self.make_request("GET", "/auth/keys", None).await?;
88 let val: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
89 VectorizerError::server(format!("Failed to parse list_api_keys response: {e}"))
90 })?;
91 let arr = val
92 .get("keys")
93 .and_then(|k| k.as_array())
94 .cloned()
95 .unwrap_or_default();
96 arr.into_iter()
97 .map(|v| {
98 serde_json::from_value(v).map_err(|e| {
99 VectorizerError::server(format!("Failed to parse api key entry: {e}"))
100 })
101 })
102 .collect()
103 }
104
105 pub async fn revoke_api_key(&self, id: &str) -> Result<()> {
110 self.make_request("DELETE", &format!("/auth/keys/{id}"), None)
111 .await?;
112 Ok(())
113 }
114
115 pub async fn create_user(&self, request: CreateUserRequest) -> Result<User> {
119 let payload = serde_json::to_value(&request).map_err(|e| {
120 VectorizerError::server(format!("Failed to serialize create_user request: {e}"))
121 })?;
122 let response = self
123 .make_request("POST", "/auth/users", Some(payload))
124 .await?;
125 serde_json::from_str(&response).map_err(|e| {
127 VectorizerError::server(format!("Failed to parse create_user response: {e}"))
128 })
129 }
130
131 pub async fn list_users(&self) -> Result<Vec<User>> {
135 let response = self.make_request("GET", "/auth/users", None).await?;
136 let val: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
137 VectorizerError::server(format!("Failed to parse list_users response: {e}"))
138 })?;
139 let arr = val
140 .get("users")
141 .and_then(|u| u.as_array())
142 .cloned()
143 .unwrap_or_default();
144 arr.into_iter()
145 .map(|v| {
146 serde_json::from_value(v).map_err(|e| {
147 VectorizerError::server(format!("Failed to parse user entry: {e}"))
148 })
149 })
150 .collect()
151 }
152
153 pub async fn delete_user(&self, username: &str) -> Result<()> {
158 self.make_request("DELETE", &format!("/auth/users/{username}"), None)
159 .await?;
160 Ok(())
161 }
162
163 pub async fn change_password(&self, username: &str, new_password: &str) -> Result<()> {
169 let payload = serde_json::json!({ "new_password": new_password });
170 self.make_request(
171 "PUT",
172 &format!("/auth/users/{username}/password"),
173 Some(payload),
174 )
175 .await?;
176 Ok(())
177 }
178
179 pub async fn rotate_api_key(&self, id: &str) -> Result<RotatedKey> {
187 let response = self
188 .make_request(
189 "POST",
190 &format!("/auth/keys/{id}/rotate"),
191 Some(serde_json::json!({})),
192 )
193 .await?;
194 serde_json::from_str(&response).map_err(|e| {
195 VectorizerError::server(format!("Failed to parse rotate_api_key response: {e}"))
196 })
197 }
198
199 pub async fn create_scoped_api_key(
205 &self,
206 request: CreateScopedApiKeyRequest,
207 ) -> Result<ApiKey> {
208 let payload = serde_json::to_value(&request).map_err(|e| {
209 VectorizerError::server(format!(
210 "Failed to serialize create_scoped_api_key request: {e}"
211 ))
212 })?;
213 let response = self
214 .make_request("POST", "/auth/keys", Some(payload))
215 .await?;
216 serde_json::from_str(&response).map_err(|e| {
217 VectorizerError::server(format!(
218 "Failed to parse create_scoped_api_key response: {e}"
219 ))
220 })
221 }
222
223 pub async fn update_api_key_permissions(
232 &self,
233 id: &str,
234 request: UpdateApiKeyPermissionsRequest,
235 ) -> Result<ApiKeyView> {
236 let payload = serde_json::to_value(&request).map_err(|e| {
237 VectorizerError::server(format!(
238 "Failed to serialize update_api_key_permissions request: {e}"
239 ))
240 })?;
241 let response = self
242 .make_request(
243 "PUT",
244 &format!("/auth/keys/{id}/permissions"),
245 Some(payload),
246 )
247 .await?;
248 serde_json::from_str(&response).map_err(|e| {
249 VectorizerError::server(format!(
250 "Failed to parse update_api_key_permissions response: {e}"
251 ))
252 })
253 }
254
255 pub async fn get_api_key_usage(
262 &self,
263 id: &str,
264 window_days: Option<usize>,
265 ) -> Result<ApiKeyUsageReport> {
266 let path = match window_days {
267 Some(n) => format!("/auth/keys/{id}/usage?window={n}"),
268 None => format!("/auth/keys/{id}/usage"),
269 };
270 let response = self.make_request("GET", &path, None).await?;
271 serde_json::from_str(&response).map_err(|e| {
272 VectorizerError::server(format!("Failed to parse get_api_key_usage response: {e}"))
273 })
274 }
275
276 pub async fn introspect_token(&self, token: &str) -> Result<TokenIntrospection> {
281 let payload = serde_json::json!({ "token": token });
282 let response = self
283 .make_request("POST", "/auth/introspect", Some(payload))
284 .await?;
285 serde_json::from_str(&response).map_err(|e| {
286 VectorizerError::server(format!("Failed to parse introspect_token response: {e}"))
287 })
288 }
289
290 pub async fn list_audit_log(&self, query: AuditQuery) -> Result<Vec<AuditEntry>> {
295 let mut parts: Vec<String> = Vec::new();
298 if let Some(actor) = &query.actor {
299 parts.push(format!("actor={actor}"));
300 }
301 if let Some(action) = &query.action {
302 parts.push(format!("action={action}"));
303 }
304 if let Some(since) = &query.since {
305 parts.push(format!("since={since}"));
306 }
307 if let Some(until) = &query.until {
308 parts.push(format!("until={until}"));
309 }
310 if let Some(limit) = query.limit {
311 parts.push(format!("limit={limit}"));
312 }
313 let path = if parts.is_empty() {
314 "/auth/audit".to_string()
315 } else {
316 format!("/auth/audit?{}", parts.join("&"))
317 };
318 let response = self.make_request("GET", &path, None).await?;
319 let val: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
320 VectorizerError::server(format!("Failed to parse list_audit_log response: {e}"))
321 })?;
322 let arr = val
323 .get("entries")
324 .and_then(|e| e.as_array())
325 .cloned()
326 .unwrap_or_default();
327 arr.into_iter()
328 .map(|v| {
329 serde_json::from_value(v).map_err(|e| {
330 VectorizerError::server(format!("Failed to parse audit entry: {e}"))
331 })
332 })
333 .collect()
334 }
335}
336
337#[cfg(test)]
338mod tests {
339 #![allow(clippy::unwrap_used)]
340
341 use serde_json::json;
342
343 use crate::models::{
344 ApiKey, CreateApiKeyRequest, CreateUserRequest, JwtToken, PasswordPolicyReport, User,
345 };
346
347 #[test]
348 fn user_deserializes() {
349 let raw = json!({
350 "user_id": "u-1",
351 "username": "alice",
352 "roles": ["Admin", "User"]
353 });
354 let u: User = serde_json::from_value(raw).unwrap();
355 assert_eq!(u.username, "alice");
356 assert_eq!(u.roles.len(), 2);
357 }
358
359 #[test]
360 fn user_round_trip() {
361 let u = User {
362 user_id: "u-42".into(),
363 username: "bob".into(),
364 roles: vec!["User".into()],
365 };
366 let serialized = serde_json::to_value(&u).unwrap();
367 let parsed: User = serde_json::from_value(serialized).unwrap();
368 assert_eq!(parsed.user_id, "u-42");
369 }
370
371 #[test]
372 fn jwt_token_deserializes() {
373 let raw = json!({
374 "access_token": "eyJ...",
375 "token_type": "Bearer",
376 "expires_in": 3600
377 });
378 let t: JwtToken = serde_json::from_value(raw).unwrap();
379 assert_eq!(t.token_type, "Bearer");
380 assert_eq!(t.expires_in, 3600);
381 }
382
383 #[test]
384 fn password_policy_report_valid() {
385 let raw = json!({
386 "valid": true,
387 "errors": [],
388 "strength": 80,
389 "strength_label": "Strong"
390 });
391 let r: PasswordPolicyReport = serde_json::from_value(raw).unwrap();
392 assert!(r.valid);
393 assert_eq!(r.strength, 80);
394 assert_eq!(r.strength_label, "Strong");
395 }
396
397 #[test]
398 fn password_policy_report_invalid() {
399 let raw = json!({
400 "valid": false,
401 "errors": ["too short", "needs uppercase"],
402 "strength": 10,
403 "strength_label": "Very Weak"
404 });
405 let r: PasswordPolicyReport = serde_json::from_value(raw).unwrap();
406 assert!(!r.valid);
407 assert_eq!(r.errors.len(), 2);
408 }
409
410 #[test]
411 fn create_api_key_request_serializes() {
412 let req = CreateApiKeyRequest {
413 name: "ci-bot".into(),
414 permissions: vec!["Read".into(), "Write".into()],
415 expires_in: Some(86400),
416 };
417 let v = serde_json::to_value(&req).unwrap();
418 assert_eq!(v["name"], "ci-bot");
419 assert_eq!(v["expires_in"], 86400);
420 }
421
422 #[test]
423 fn api_key_deserializes_creation_response() {
424 let raw = json!({
425 "id": "key-1",
426 "name": "ci-bot",
427 "permissions": ["Read"],
428 "api_key": "sk-abc123",
429 "created_at": 1714608000u64,
430 "active": true,
431 "warning": "Store this key securely"
432 });
433 let k: ApiKey = serde_json::from_value(raw).unwrap();
434 assert_eq!(k.id, "key-1");
435 assert_eq!(k.api_key.as_deref(), Some("sk-abc123"));
436 assert!(k.active);
437 }
438
439 #[test]
440 fn api_key_deserializes_list_response() {
441 let raw = json!({
443 "id": "key-2",
444 "name": "deploy",
445 "permissions": ["Write"],
446 "created_at": 1714608000u64,
447 "active": false
448 });
449 let k: ApiKey = serde_json::from_value(raw).unwrap();
450 assert!(k.api_key.is_none());
451 assert!(!k.active);
452 }
453
454 #[test]
455 fn create_user_request_serializes() {
456 let req = CreateUserRequest {
457 username: "charlie".into(),
458 password: "P@ssw0rd!".into(),
459 roles: vec!["User".into()],
460 };
461 let v = serde_json::to_value(&req).unwrap();
462 assert_eq!(v["username"], "charlie");
463 }
464
465 use crate::models::{
468 AuditEntry, AuditQuery, CreateScopedApiKeyRequest, RotatedKey, TokenIntrospection,
469 TokenScope,
470 };
471
472 #[test]
473 fn rotated_key_deserializes() {
474 let raw = json!({
475 "old_key_id": "key-old",
476 "new_key_id": "key-new",
477 "new_token": "sk-new-token",
478 "grace_until": 1714694400u64
479 });
480 let r: RotatedKey = serde_json::from_value(raw).unwrap();
481 assert_eq!(r.old_key_id, "key-old");
482 assert_eq!(r.new_key_id, "key-new");
483 assert_eq!(r.grace_until, 1714694400);
484 }
485
486 #[test]
487 fn create_scoped_api_key_request_serializes() {
488 let req = CreateScopedApiKeyRequest {
489 name: "scoped-key".into(),
490 permissions: vec!["Read".into()],
491 expires_in: Some(3600),
492 scopes: vec![TokenScope {
493 collection: "my-col".into(),
494 permissions: vec!["read".into(), "write".into()],
495 }],
496 };
497 let v = serde_json::to_value(&req).unwrap();
498 assert_eq!(v["name"], "scoped-key");
499 assert_eq!(v["scopes"][0]["collection"], "my-col");
500 assert_eq!(v["scopes"][0]["permissions"][1], "write");
501 }
502
503 #[test]
504 fn token_introspection_active_deserializes() {
505 let raw = json!({
506 "active": true,
507 "sub": "user-1",
508 "exp": 1714694400u64,
509 "username": "alice"
510 });
511 let t: TokenIntrospection = serde_json::from_value(raw).unwrap();
512 assert!(t.active);
513 assert_eq!(t.sub.as_deref(), Some("user-1"));
514 assert_eq!(t.username.as_deref(), Some("alice"));
515 assert!(t.scope.is_none());
516 }
517
518 #[test]
519 fn token_introspection_inactive_deserializes() {
520 let raw = json!({ "active": false });
521 let t: TokenIntrospection = serde_json::from_value(raw).unwrap();
522 assert!(!t.active);
523 assert!(t.sub.is_none());
524 assert!(t.exp.is_none());
525 }
526
527 #[test]
528 fn audit_entry_deserializes() {
529 let raw = json!({
530 "actor": "admin",
531 "action": "rotate_api_key",
532 "target": "key-1",
533 "at": "2026-05-02T12:00:00Z",
534 "correlation_id": "corr-abc"
535 });
536 let e: AuditEntry = serde_json::from_value(raw).unwrap();
537 assert_eq!(e.actor, "admin");
538 assert_eq!(e.action, "rotate_api_key");
539 assert_eq!(e.correlation_id.as_deref(), Some("corr-abc"));
540 }
541
542 #[test]
543 fn audit_entry_without_correlation_id_deserializes() {
544 let raw = json!({
545 "actor": "admin",
546 "action": "create_api_key",
547 "target": "key-2",
548 "at": "2026-05-02T13:00:00Z"
549 });
550 let e: AuditEntry = serde_json::from_value(raw).unwrap();
551 assert!(e.correlation_id.is_none());
552 }
553
554 #[test]
555 fn audit_query_serializes_with_defaults() {
556 let q = AuditQuery::default();
557 let v = serde_json::to_value(&q).unwrap();
558 assert_eq!(v, json!({}));
560 }
561
562 #[test]
563 fn audit_query_serializes_partial() {
564 let q = AuditQuery {
565 actor: Some("admin".into()),
566 limit: Some(50),
567 ..Default::default()
568 };
569 let v = serde_json::to_value(&q).unwrap();
570 assert_eq!(v["actor"], "admin");
571 assert_eq!(v["limit"], 50);
572 assert!(v.get("action").is_none());
573 }
574}