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