passage_auth/
passage.rs

1use serde::{de::DeserializeOwned, Serialize};
2
3use crate::{
4    apis::{
5        Authenticate, CurrentUser, Jwks, Login, MagicLink, OpenId, Otp, Register, Tokens, Users,
6    },
7    config::Config,
8    error::ApiError,
9    PassageError,
10};
11
12#[derive(Debug, Clone)]
13pub struct Passage {
14    http_client: reqwest::Client,
15    config: Config,
16}
17
18impl Default for Passage {
19    fn default() -> Self {
20        Self::new()
21    }
22}
23
24impl Passage {
25    /// Creates a new [Passage] for interacting with the Passage API.
26    pub fn new() -> Self {
27        Self {
28            http_client: reqwest::Client::new(),
29            config: Config::default(),
30        }
31    }
32}
33
34impl Passage {
35    /// Create client with a custom HTTP client if needed.
36    pub fn build(http_client: reqwest::Client, config: Config) -> Self {
37        Self {
38            http_client,
39            config,
40        }
41    }
42
43    pub fn with_config(config: Config) -> Self {
44        Self {
45            http_client: reqwest::Client::new(),
46            config,
47        }
48    }
49
50    pub fn set_pub_jwk(mut self, pub_jwk: String) -> Self {
51        self.config = self.config.with_pub_jwk(pub_jwk);
52        self
53    }
54
55    /// Provide a custom [client] to make all HTTP requests with.
56    ///
57    /// [client]: reqwest::Client
58    pub fn with_http_client(mut self, http_client: reqwest::Client) -> Self {
59        self.http_client = http_client;
60        self
61    }
62
63    // API groups
64
65    /// To call the [Tokens] group related APIs using this client.
66    pub fn tokens(&self) -> Tokens {
67        Tokens::new(self)
68    }
69
70    /// To call [Register] group related APIs using this client.
71    pub fn register(&self) -> Register {
72        Register::new(self)
73    }
74
75    /// To call [Otp] group related APIs using this client.
76    pub fn otp(&self) -> Otp {
77        Otp::new(self)
78    }
79
80    /// To call [OpenId] group related APIs using this client.
81    pub fn open_id(&self) -> OpenId {
82        OpenId::new(self)
83    }
84
85    /// To call [MagicLink] group related APIs using this client.
86    pub fn magic_link(&self) -> MagicLink {
87        MagicLink::new(self)
88    }
89
90    /// To call [Login] group related APIs using this client.
91    pub fn login(&self) -> Login {
92        Login::new(self)
93    }
94
95    /// To call [Jwks] group related APIs using this client.
96    pub fn jwks(&self) -> Jwks {
97        Jwks::new(self)
98    }
99
100    /// To call [Authenticate] group related APIs using this client.
101    pub fn authenticate(&self) -> Authenticate {
102        Authenticate::new(self)
103    }
104
105    /// To call [CurrentUser] group related APIs using this client.
106    pub fn current_user(&self) -> CurrentUser {
107        CurrentUser::new(self)
108    }
109
110    /// To call [Users] group related APIs using this client.
111    pub fn users(&self) -> Users {
112        Users::new(self)
113    }
114
115    pub fn app_auth_origin(&self) -> &str {
116        self.config.app_auth_origin()
117    }
118
119    pub fn app_id(&self) -> &str {
120        self.config.app_id()
121    }
122
123    pub fn config(&self) -> &Config {
124        &self.config
125    }
126
127    pub fn pub_jwk(&self) -> Option<&String> {
128        self.config.pub_jwk()
129    }
130
131    /// Make a GET request to {path} and deserialize the response body
132    pub(crate) async fn get<O>(&self, path: &str) -> Result<O, PassageError>
133    where
134        O: DeserializeOwned,
135    {
136        let response = self
137            .http_client
138            .get(self.config.url(path))
139            .query(&self.config.query())
140            .headers(self.config.bearer_auth())
141            .send()
142            .await?;
143
144        self.deserialize_response(response).await
145    }
146
147    /// Make a GET request to {path} with given Query and deserialize the
148    /// response body
149    pub(crate) async fn get_with_query<Q, O>(
150        &self,
151        path: &str,
152        query: &Q,
153    ) -> Result<O, PassageError>
154    where
155        O: DeserializeOwned,
156        Q: Serialize + ?Sized,
157    {
158        let response = self
159            .http_client
160            .get(self.config.url(path))
161            .query(&self.config.query())
162            .query(query)
163            .headers(self.config.bearer_auth())
164            .send()
165            .await?;
166
167        self.deserialize_response(response).await
168    }
169
170    /// Make a GET request to the Passage management API and deserialize the
171    /// response body
172    ///
173    /// This is temporary until the `passage-manage` crate is created
174    pub(crate) async fn get_from_management_api<O>(&self, path: &str) -> Result<O, PassageError>
175    where
176        O: DeserializeOwned,
177    {
178        let response = self
179            .http_client
180            .get(format!("https://api.passage.id/v1{}", path))
181            .query(&self.config.query())
182            .headers(self.config.api_key_auth())
183            .send()
184            .await?;
185
186        dbg!(&response);
187
188        self.deserialize_response(response).await
189    }
190
191    /// Make a POST request to {path} and deserialize the response body
192    pub(crate) async fn post<I, O>(&self, path: &str, request: I) -> Result<O, PassageError>
193    where
194        I: Serialize,
195        O: DeserializeOwned,
196    {
197        let response = self
198            .http_client
199            .post(self.config.url(path))
200            .query(&self.config.query())
201            .headers(self.config.bearer_auth())
202            .json(&request)
203            .send()
204            .await?;
205
206        self.deserialize_response(response).await
207    }
208
209    /// Make a POST request to {patch} and deserialize the response body
210    pub(crate) async fn patch<I, O>(&self, path: &str, request: I) -> Result<O, PassageError>
211    where
212        I: Serialize,
213        O: DeserializeOwned,
214    {
215        let response = self
216            .http_client
217            .patch(self.config.url(path))
218            .query(&self.config.query())
219            .headers(self.config.bearer_auth())
220            .json(&request)
221            .send()
222            .await?;
223
224        self.deserialize_response(response).await
225    }
226
227    /// Make a DELETE request to {path}
228    pub(crate) async fn delete<O>(&self, path: &str) -> Result<O, PassageError>
229    where
230        O: DeserializeOwned,
231    {
232        let response = self
233            .http_client
234            .delete(self.config.url(path))
235            .query(&self.config.query())
236            .headers(self.config.bearer_auth())
237            .send()
238            .await?;
239
240        self.deserialize_response(response).await
241    }
242
243    /// Make a DELETE request to {path} with given Query and deserialize the
244    /// response body
245    pub(crate) async fn delete_with_query<Q, O>(
246        &self,
247        path: &str,
248        query: &Q,
249    ) -> Result<O, PassageError>
250    where
251        O: DeserializeOwned,
252        Q: Serialize + ?Sized,
253    {
254        let response = self
255            .http_client
256            .delete(self.config.url(path))
257            .query(&self.config.query())
258            .query(query)
259            .headers(self.config.bearer_auth())
260            .send()
261            .await?;
262
263        self.deserialize_response(response).await
264    }
265
266    /// TODO: This needs some love
267    async fn deserialize_response<O>(&self, response: reqwest::Response) -> Result<O, PassageError>
268    where
269        O: DeserializeOwned,
270    {
271        if response.status().is_success() {
272            response.json::<O>().await.map_err(PassageError::from)
273        } else {
274            Err(response
275                .json::<ApiError>()
276                .await
277                .map_err(PassageError::from)?
278                .into())
279        }
280    }
281}