1use std::sync::Arc;
4
5use reqwest::Client;
6use serde::{Deserialize, Serialize};
7
8use crate::error::{OramaError, Result};
9
10#[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#[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#[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#[derive(Debug, Clone)]
44pub enum AuthConfig {
45 ApiKey(ApiKeyAuth),
46 Jwt(JwtAuth),
47}
48
49#[derive(Debug, Clone)]
51pub struct AuthRef {
52 pub bearer: String,
53 pub base_url: String,
54}
55
56#[derive(Debug, Clone, PartialEq)]
58pub enum Target {
59 Reader,
60 Writer,
61}
62
63#[derive(Debug, Clone)]
65pub struct Auth {
66 config: AuthConfig,
67 client: Arc<Client>,
68}
69
70impl Auth {
71 pub fn new(config: AuthConfig, client: Arc<Client>) -> Self {
73 Self { config, client }
74 }
75
76 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 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 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 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 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 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 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 pub fn with_writer_url<S: Into<String>>(mut self, url: S) -> Self {
207 self.writer_url = Some(url.into());
208 self
209 }
210}