metabase_api_rs/service/
auth.rs

1//! Authentication service implementation
2//!
3//! This module provides business logic for authentication operations.
4
5use super::traits::{Service, ServiceError, ServiceResult};
6use crate::api::auth::Credentials;
7use crate::core::models::User;
8use crate::transport::http_provider_safe::{HttpProviderExt, HttpProviderSafe};
9use async_trait::async_trait;
10use secrecy::ExposeSecret;
11use serde::Deserialize;
12use serde_json::json;
13use std::sync::Arc;
14
15/// Service trait for Authentication operations
16#[async_trait]
17pub trait AuthService: Service {
18    /// Authenticate with the API
19    async fn authenticate(&self, credentials: Credentials) -> ServiceResult<(String, User)>;
20
21    /// Logout from the API
22    async fn logout(&self, session_id: &str) -> ServiceResult<()>;
23
24    /// Get current user information
25    async fn get_current_user(&self, session_id: &str) -> ServiceResult<User>;
26
27    /// Validate session
28    async fn validate_session(&self, session_id: &str) -> ServiceResult<bool>;
29
30    /// Health check
31    async fn health_check(&self) -> ServiceResult<crate::core::models::HealthStatus>;
32}
33
34/// HTTP implementation of AuthService
35pub struct HttpAuthService {
36    http_provider: Arc<dyn HttpProviderSafe>,
37}
38
39impl HttpAuthService {
40    /// Create a new HTTP auth service
41    pub fn new(http_provider: Arc<dyn HttpProviderSafe>) -> Self {
42        Self { http_provider }
43    }
44}
45
46#[async_trait]
47impl Service for HttpAuthService {
48    fn name(&self) -> &str {
49        "AuthService"
50    }
51}
52
53#[async_trait]
54impl AuthService for HttpAuthService {
55    async fn authenticate(&self, credentials: Credentials) -> ServiceResult<(String, User)> {
56        let request_body = match &credentials {
57            Credentials::EmailPassword { email, password } => {
58                json!({
59                    "username": email,
60                    "password": password.expose_secret()
61                })
62            }
63            Credentials::ApiKey { key } => {
64                json!({
65                    "api_key": key.expose_secret()
66                })
67            }
68        };
69
70        #[derive(Deserialize)]
71        struct SessionResponse {
72            id: String,
73            #[serde(flatten)]
74            user_data: serde_json::Value,
75        }
76
77        let response: SessionResponse = self
78            .http_provider
79            .post("/api/session", &request_body)
80            .await
81            .map_err(ServiceError::from)?;
82
83        // Parse user information
84        use crate::core::models::common::UserId;
85
86        let user = User {
87            id: UserId(response.user_data["id"].as_i64().unwrap_or(1)),
88            email: response.user_data["email"]
89                .as_str()
90                .unwrap_or("unknown@example.com")
91                .to_string(),
92            first_name: response.user_data["first_name"]
93                .as_str()
94                .unwrap_or("Unknown")
95                .to_string(),
96            last_name: response.user_data["last_name"]
97                .as_str()
98                .unwrap_or("User")
99                .to_string(),
100            is_superuser: response.user_data["is_superuser"]
101                .as_bool()
102                .unwrap_or(false),
103            is_active: true,
104            is_qbnewb: response.user_data["is_qbnewb"].as_bool().unwrap_or(false),
105            date_joined: chrono::Utc::now(),
106            last_login: Some(chrono::Utc::now()),
107            common_name: None,
108            group_ids: Vec::new(),
109            locale: response.user_data["locale"].as_str().map(|s| s.to_string()),
110            google_auth: response.user_data["google_auth"].as_bool().unwrap_or(false),
111            ldap_auth: response.user_data["ldap_auth"].as_bool().unwrap_or(false),
112            login_attributes: None,
113            user_group_memberships: Vec::new(),
114        };
115
116        Ok((response.id, user))
117    }
118
119    async fn logout(&self, _session_id: &str) -> ServiceResult<()> {
120        self.http_provider
121            .delete_json("/api/session")
122            .await
123            .map(|_: serde_json::Value| ())
124            .map_err(ServiceError::from)
125    }
126
127    async fn get_current_user(&self, _session_id: &str) -> ServiceResult<User> {
128        self.http_provider
129            .get("/api/user/current")
130            .await
131            .map_err(ServiceError::from)
132    }
133
134    async fn validate_session(&self, session_id: &str) -> ServiceResult<bool> {
135        // Try to get current user to validate session
136        match self.get_current_user(session_id).await {
137            Ok(_) => Ok(true),
138            Err(_) => Ok(false),
139        }
140    }
141
142    async fn health_check(&self) -> ServiceResult<crate::core::models::HealthStatus> {
143        self.http_provider
144            .get("/api/health")
145            .await
146            .map_err(ServiceError::from)
147    }
148}