Skip to main content

oramacore_client/
auth.rs

1//! Authentication handling for Orama client.
2
3use std::sync::Arc;
4
5use reqwest::Client;
6use serde::{Deserialize, Serialize};
7
8use crate::error::{OramaError, Result};
9
10/// JWT response from authentication endpoint
11#[derive(Debug, Clone, Serialize, Deserialize)]
12struct JwtRequestResponse {
13    jwt: String,
14    #[serde(rename = "writerURL")]
15    writer_url: String,
16    #[serde(rename = "readerApiKey")]
17    reader_api_key: String,
18    #[serde(rename = "readerURL")]
19    reader_url: String,
20    #[serde(rename = "expiresIn")]
21    expires_in: u64,
22}
23
24/// Authentication configuration for API key authentication
25#[derive(Debug, Clone)]
26pub struct ApiKeyAuth {
27    pub api_key: String,
28    pub reader_url: Option<String>,
29    pub writer_url: Option<String>,
30}
31
32/// Authentication configuration for JWT authentication
33#[derive(Debug, Clone)]
34pub struct JwtAuth {
35    pub auth_jwt_url: String,
36    pub collection_id: String,
37    pub private_api_key: String,
38    pub reader_url: Option<String>,
39    pub writer_url: Option<String>,
40}
41
42/// Authentication configuration enum
43#[derive(Debug, Clone)]
44pub enum AuthConfig {
45    ApiKey(ApiKeyAuth),
46    Jwt(JwtAuth),
47}
48
49/// Authentication reference containing bearer token and base URL
50#[derive(Debug, Clone)]
51pub struct AuthRef {
52    pub bearer: String,
53    pub base_url: String,
54}
55
56/// Target for the request (reader or writer)
57#[derive(Debug, Clone, PartialEq)]
58pub enum Target {
59    Reader,
60    Writer,
61}
62
63/// Authentication handler
64#[derive(Debug, Clone)]
65pub struct Auth {
66    config: AuthConfig,
67    client: Arc<Client>,
68}
69
70impl Auth {
71    /// Create a new authentication handler
72    pub fn new(config: AuthConfig, client: Arc<Client>) -> Self {
73        Self { config, client }
74    }
75
76    /// Get authentication reference for the specified target
77    pub async fn get_ref(&self, target: Target) -> Result<AuthRef> {
78        match &self.config {
79            AuthConfig::ApiKey(config) => {
80                let bearer = config.api_key.clone();
81                let base_url = match target {
82                    Target::Writer => {
83                        config.writer_url.as_ref()
84                            .ok_or_else(|| OramaError::config(
85                                "Cannot perform a request to a writer without the writerURL. Use cluster.writerURL to configure it"
86                            ))?
87                            .clone()
88                    }
89                    Target::Reader => {
90                        config.reader_url.as_ref()
91                            .ok_or_else(|| OramaError::config(
92                                "Cannot perform a request to a reader without the readerURL. Use cluster.readerURL to configure it"
93                            ))?
94                            .clone()
95                    }
96                };
97
98                Ok(AuthRef { bearer, base_url })
99            }
100            AuthConfig::Jwt(config) => {
101                let jwt_response = self
102                    .get_jwt_token(
103                        &config.auth_jwt_url,
104                        &config.collection_id,
105                        &config.private_api_key,
106                        "write",
107                    )
108                    .await?;
109
110                let (bearer, base_url) = match target {
111                    Target::Reader => {
112                        let base_url = config
113                            .reader_url
114                            .as_ref()
115                            .unwrap_or(&jwt_response.reader_url)
116                            .clone();
117                        (jwt_response.reader_api_key, base_url)
118                    }
119                    Target::Writer => {
120                        let base_url = config
121                            .writer_url
122                            .as_ref()
123                            .unwrap_or(&jwt_response.writer_url)
124                            .clone();
125                        (jwt_response.jwt, base_url)
126                    }
127                };
128
129                Ok(AuthRef { bearer, base_url })
130            }
131        }
132    }
133
134    /// Get JWT token from authentication endpoint
135    async fn get_jwt_token(
136        &self,
137        auth_jwt_url: &str,
138        collection_id: &str,
139        private_api_key: &str,
140        scope: &str,
141    ) -> Result<JwtRequestResponse> {
142        let payload = serde_json::json!({
143            "collectionId": collection_id,
144            "privateApiKey": private_api_key,
145            "scope": scope
146        });
147
148        let response = self.client.post(auth_jwt_url).json(&payload).send().await?;
149
150        if !response.status().is_success() {
151            let status = response.status().as_u16();
152            let text = response.text().await.unwrap_or_default();
153            return Err(OramaError::api(
154                status,
155                format!("JWT request to {auth_jwt_url} failed: {text}"),
156            ));
157        }
158
159        let jwt_response: JwtRequestResponse = response.json().await?;
160        Ok(jwt_response)
161    }
162}
163
164impl ApiKeyAuth {
165    /// Create a new API key authentication configuration
166    pub fn new<S: Into<String>>(api_key: S) -> Self {
167        Self {
168            api_key: api_key.into(),
169            reader_url: None,
170            writer_url: None,
171        }
172    }
173
174    /// Set the reader URL
175    pub fn with_reader_url<S: Into<String>>(mut self, url: S) -> Self {
176        self.reader_url = Some(url.into());
177        self
178    }
179
180    /// Set the writer URL
181    pub fn with_writer_url<S: Into<String>>(mut self, url: S) -> Self {
182        self.writer_url = Some(url.into());
183        self
184    }
185}
186
187impl JwtAuth {
188    /// Create a new JWT authentication configuration
189    pub fn new<S: Into<String>>(auth_jwt_url: S, collection_id: S, private_api_key: S) -> Self {
190        Self {
191            auth_jwt_url: auth_jwt_url.into(),
192            collection_id: collection_id.into(),
193            private_api_key: private_api_key.into(),
194            reader_url: None,
195            writer_url: None,
196        }
197    }
198
199    /// Set the reader URL
200    pub fn with_reader_url<S: Into<String>>(mut self, url: S) -> Self {
201        self.reader_url = Some(url.into());
202        self
203    }
204
205    /// Set the writer URL
206    pub fn with_writer_url<S: Into<String>>(mut self, url: S) -> Self {
207        self.writer_url = Some(url.into());
208        self
209    }
210}