license_api/auth/
authenticator.rs

1use std::collections::HashMap;
2use std::error::Error;
3use log::debug;
4use reqwest::StatusCode;
5use crate::auth::models::{ErrorResponse, LoginRequest, LoginResponse, MeResponse};
6use crate::auth::traits::Authenticator;
7
8pub const NOT_LINKED: &str = "not_linked";
9
10pub struct BasicAuthenticator {
11    pub base_url: String,
12    pub client: reqwest::Client,
13}
14
15impl BasicAuthenticator {
16    pub fn new(base_url: impl Into<String>) -> Self {
17        BasicAuthenticator {
18            base_url: base_url.into(),
19            client: reqwest::Client::new(),
20        }
21    }
22}
23
24#[async_trait::async_trait]
25impl Authenticator for BasicAuthenticator {
26    async fn login(
27        &self,
28        creds: &LoginRequest,
29        hwid: &str,
30    ) -> Result<LoginResponse, Box<dyn Error + Send + Sync>> {
31        let url = format!("{}/auth/login", self.base_url.trim_end_matches('/'));
32
33        let resp = self.client.post(&url).form(&creds).send().await?;
34
35        match resp.status() {
36            StatusCode::OK | StatusCode::CREATED => {
37                let lr = resp.json::<LoginResponse>().await?;
38
39                let me = &self.me(&lr.access_token).await?;
40
41                match me.hwid.as_str() {
42                    NOT_LINKED => {
43                        let _ = &self
44                            .link_hwid(&hwid, &lr.access_token)
45                            .await?;
46                        debug!("hwid successfully linked {}", me.username);
47                    }
48                    h if h == hwid => {
49                        debug!("welcome back, {}!", me.username);
50                    }
51                    _ => {
52                        debug!("hwid mismatch! access denied.");
53                    }
54                }
55
56                debug!("login successful");
57
58                Ok(lr)
59            }
60            StatusCode::UNAUTHORIZED | StatusCode::BAD_REQUEST => {
61                let err = resp.json::<ErrorResponse>().await?;
62                Err(format!("{}", err.detail).into())
63            }
64            other => Err(format!("unexpected response status: {}", other).into()),
65        }
66    }
67
68    async fn me(&self, access_token: &str) -> Result<MeResponse, Box<dyn Error + Send + Sync>> {
69        let url = format!("{}/users/me", self.base_url.trim_end_matches('/'));
70
71        let resp = self
72            .client
73            .get(&url)
74            .bearer_auth(access_token)
75            .send()
76            .await?;
77
78        match resp.status() {
79            StatusCode::OK => {
80                let mr = resp.json::<MeResponse>().await?;
81                debug!("getting user info...");
82
83                Ok(mr)
84            }
85            StatusCode::UNAUTHORIZED | StatusCode::BAD_REQUEST => {
86                let err = resp.json::<ErrorResponse>().await?;
87                Err(format!("{}", err.detail).into())
88            }
89            other => Err(format!("unexpected response status: {}", other).into()),
90        }
91    }
92
93    async fn link_hwid(
94        &self,
95        hwid: &str,
96        access_token: &str,
97    ) -> Result<String, Box<dyn Error + Send + Sync>> {
98        let url = format!("{}/users/hwid", self.base_url.trim_end_matches('/'));
99
100        let mut map = HashMap::new();
101        map.insert("value", hwid);
102
103        let resp = self
104            .client
105            .patch(&url)
106            .json(&map)
107            .bearer_auth(access_token)
108            .send()
109            .await?;
110
111        match resp.status() {
112            StatusCode::OK => {
113                debug!("hwid successfully linked");
114
115                Ok(resp.text().await?)
116            },
117            StatusCode::UNAUTHORIZED | StatusCode::BAD_REQUEST => {
118                let err = resp.json::<ErrorResponse>().await?;
119                Err(format!("link failed: {}", err.detail).into())
120            }
121            other => Err(format!("unexpected response status: {}", other).into()),
122        }
123    }
124}